简体   繁体   English

如何处理具有密集逻辑的 React 组件?

[英]How to handle React components that have intensive logic?

Template Question模板问题

The Problem问题

Hey guys!大家好!

Recently I've been noticing some very logic-intensive react components being created in the application I work in, and this components usually have dozens of line that I call "setup logic" (variables that needs to be set before I render my component).最近我注意到在我工作的应用程序中创建了一些非常逻辑密集型的反应组件,这些组件通常有几十行,我称之为“设置逻辑”(在我呈现我的组件之前需要设置的变量) . Some of this stuff can be:其中一些东西可以是:

  • Sending a request to the translation service to retrieve the required translated strings for that component向翻译服务发送请求以检索该组件所需的翻译字符串
  • Creating derived data from states, such as:从状态创建派生数据,例如:
    • whether or not to show a component given a feature flag from the API是否显示给定来自 API 的功能标志的组件
    • The percentage of a progress bar given an initial and final value received by the API给定 API 接收的初始值和最终值的进度条的百分比
  • Getting the currentUser获取当前用户
  • Getting the default brand colours and font sizes获取默认的品牌颜色和字体大小
  • Mutations/Queries that will be executed when the user clicks on a button当用户单击按钮时将执行的突变/查询
  • Mapping all this info to styled components and child components将所有这些信息映射到样式组件和子组件

This begs the question of "Are we doing too much in these components?"这就引出了一个问题:“我们在这些组件中做得太多了吗?”

Undoubtedly yes, but how exactly do we break this apart?毫无疑问是的,但我们究竟如何将其分开? Where do we draw the line?我们在哪里画线?

Ah, and a heads up, this is our setup currently:啊,请注意,这是我们目前的设置:

Stack info:堆栈信息:

Frontend: React前端:反应

API: GraphQL接口:GraphQL

Possible Solution可能的解决方案

Separating the setup logic (the getters, mappers and queries) from the presentation logic (the JSX).将设置逻辑(getter、mapper 和查询)与表示逻辑(JSX)分开。

Question 1. Should there be a component that's sole responsibility is to serve the presentation component with its logic?问题 1.是否应该有一个组件的唯一职责是为呈现组件及其逻辑提供服务?

Question 2. Should this be a component at all or should this logic be served by the graphQL API?问题 2.这应该是一个组件还是应该由 graphQL API 提供这个逻辑? How coupled should the GraphQL be to the React component? GraphQL 应该如何与 React 组件耦合?

Sample Code示例代码

This is an example that illustrates the issue:这是一个说明问题的示例:

// 30 lines of *import*
// ...

const MyForm = ({
  onSubmit,
  initialValues,
  children,
  loading,
  isCreating,
  cycleId,
  permissions,
  otherPermissions,
  showWeightBalance,
  balance,
}) => {
  const { t } = useTranslation();
  const currentUser = useCurrentUser();
  const cycle = useCycle({ id: cycleId });
  const canUpdateFields =
    isCreating || (permissions && permissions.update);
  const canUpdateContributors =
    isCreating ||
    (permissions && permissions.updateContributors);
  const canReassignResponsible =
    isCreating ||
    (permissions && permissions.reassignResponsible);
  const canUpdateWeight =
    isCreating ||
    (otherPermissions &&
      otherPermissions.updateWeight);
  const canShowContributorsInput =
    isCreating ||
    (permissions && permissions.showContributorsInputOnForm);

  return (
    <Form
      initialValues={{
        name: {},
        description: {},
        type: {
          kind: kind.NUMBER,
          direction: direction.ASC,
        },
        baseValue: null,
        target: null,
        unit: null,
        weight: 1,
        progressCalculus: false,
        responsible: null,
        contributors: [],
        tasks: [],
        scale: null,
        ...initialValues,
      }}
      onSubmit={onSubmit}
      key={JSON.stringify(initialValues)} // This is made to reset the form when new initial values get loaded, should be enableReinitialize but richtexteditor wouldn't reset
    >
      {formProps => (
        <Fragment>
          <FormFieldTextTranslations
            name="name"
            label={t('yml_path')}
            subtitle={t('yml_path')}
            placeholder={t('yml_path')}
            locales={locale.availableLocales()}
            validate={[
              requiredTranslation(t('yml_path')),
            ]}
            disabled={loading || !canUpdateFields}
          />
          <FormFieldRichTextTranslations
            name="description"
            label={t('yml_path')}
            subtitle={t('yml_path')}
            placeholder={t(
              'yml_path',
            )}
            locales={locale.availableLocales()}
            optional
            hideToolbar
            minimumLines={4}
          />
          <FormFieldGroup
            name="type"
            label={t('yml_path')}
            validate={[required('yml_path')]}
          >
            <Layout display="flex" flexWrap="wrap">
              <FormFieldRadio
                mr="px32"
                mb={['px8', 'none']}
                disabled={loading || !isCreating}
                name="type"
                value={{
                  kind: kind.NUMBER,
                  direction: direction.ASC,
                }}
                label={(checked, disabled, error) => (
                  <RadioCard
                    iconProps={{
                      iconName: 'chart-line',
                      solid: true,
                      fontSize: '20px',
                    }}
                    text={t(
                      'yml_path',
                    )}
                    checked={checked}
                    disabled={disabled}
                    error={error}
                  />
                )}
              />
              <FormFieldRadio
                mr="px32"
                mb={['px8', 'none']}
                disabled={loading || !isCreating}
                name="type"
                value={{
                  kind: kind.NUMBER,
                  direction: direction.DESC,
                }}
                label={(checked, disabled, error) => (
                  <RadioCard
                    iconProps={{
                      iconName: 'chart-line-down',
                      solid: true,
                      fontSize: '20px',
                    }}
                    text={t(
                      'yml_path',
                    )}
                    checked={checked}
                    disabled={disabled}
                    error={error}
                  />
                )}
              />
              <FormFieldRadio
                mr="px32"
                mb={['px8', 'none']}
                disabled={loading || !isCreating}
                name="type"
                value={{
                  kind: kind.KEEP,
                  direction: formProps.values.type.direction,
                }}
                label={(checked, disabled, error) => (
                  <RadioCard
                    iconProps={{
                      iconName: 'chart-keep',
                      solid: true,
                      fontSize: '20px',
                    }}
                    text={t('yml_path')}
                    checked={checked}
                    disabled={disabled}
                    error={error}
                  />
                )}
              />
              <FormFieldRadio
                mb={['px8', 'none']}
                disabled={loading || !isCreating}
                name="type"
                value={{
                  kind: kind.BINARY,
                  direction: null,
                }}
                label={(checked, disabled, error) => (
                  <RadioCard
                    iconProps={{
                      iconName: 'check',
                      solid: true,
                      fontSize: '20px',
                    }}
                    text={t(
                      'yml_path',
                    )}
                    checked={checked}
                    disabled={disabled}
                    error={error}
                    tooltip={t(
                      'yml_path',
                    )}
                  />
                )}
              />
            </Layout>
          </FormFieldGroup>
          {formProps.values.type.kind === kind.NUMBER &&
            formProps.values.type.direction === direction.ASC && (
              <AscendingForm
                formProps={formProps}
                loading={loading}
                canUpdateFields={canUpdateFields}
                canUpdateWeight={canUpdateWeight}
                showWeightBalance={showWeightBalance}
                balance={balance}
                isCreating={isCreating}
              />
            )}
          {formProps.values.type.kind === kind.NUMBER &&
            formProps.values.type.direction === direction.DESC && (
              <DescendingForm
                formProps={formProps}
                loading={loading}
                canUpdateFields={canUpdateFields}
                canUpdateWeight={canUpdateWeight}
                showWeightBalance={showWeightBalance}
                balance={balance}
                isCreating={isCreating}
              />
            )}
          {formProps.values.type.kind === kind.KEEP && (
            <KeepForm
              loading={loading}
              canUpdateFields={canUpdateFields}
              canUpdateWeight={canUpdateWeight}
              showWeightBalance={showWeightBalance}
              balance={balance}
              isCreating={isCreating}
            />
          )}
          {formProps.values.type.kind === kind.BINARY && (
            <BinaryForm
              loading={loading}
              canUpdateWeight={canUpdateWeight}
              showWeightBalance={showWeightBalance}
              balance={balance}
              isCreating={isCreating}
            />
          )}
          {cycle &&
            (cycle.allowCustomScore ||
              cycle.allowScale) &&
            formProps.values.type.kind !== kind.BINARY && (
              <FormFieldGroup
                name="progressCalculus"
                label={t('yml_path')}
                validate={[
                  required(t('yml_path')),
                ]}
              >
                <FormFieldRadio
                  name="progressCalculus"
                  mb="px4"
                  label={t(
                    'yml_path',
                  )}
                  disabled={loading || !isCreating}
                  value={false}
                />
                {cycle.allowCustomScore && (
                  <FormFieldRadio
                    name="progressCalculus"
                    mb="px4"
                    label={t(
                      'yml_path',
                    )}
                    disabled={loading || !isCreating}
                    value={ProgressCalculusEnum.customScore}
                  />
                )}
                {cycle.allowScoreScale && (
                  <Fragment>
                    <FormFieldRadio
                      name="progressCalculus"
                      mb="px4"
                      label={t(
                        'yml_path',
                      )}
                      disabled={loading || !isCreating}
                      value={ProgressCalculusEnum.scale}
                    />
                    <ScaleRadioHelperNegativeMargin>
                      <FieldHelper
                        message={t(
                          'yml_path',
                        )}
                        iconName="info-circle"
                      />
                    </ScaleRadioHelperNegativeMargin>
                  </Fragment>
                )}
              </FormFieldGroup>
            )}
          {isCreating &&
            cycle &&
            cycle.allowScoreScale &&
            formProps.values.type.kind === kind.NUMBER &&
            formProps.values.progressCalculus ===
              ProgressCalculusEnum.scale && (
              <ScoreScaleForm
                name="scale.partitions"
                formProps={formProps}
              />
            )}
          <FormFieldSelectContract
            name="responsible"
            label={t('yml_path')}
            placeholder={t(
              'yml_path',
            )}
            filter={{ active: true }}
            validate={[
              required(
                t('yml_path'),
              ),
            ]}
            disabled={loading || !canReassignResponsible}
            allowClear
          />
          <AssignToMeWrapper>
            <Button
              kind="primary"
              size="adaptative"
              appearance="text"
              onMouseDown={() => {
                setTimeout(
                  () =>
                    formProps.setFieldValue('responsible', {
                      key: currentUser.id,
                      label: currentUser.name,
                    }),
                  20,
                );
              }}
            >
              {t('assign_to_me')}
            </Button>
          </AssignToMeWrapper>
          {canShowContributorsInput && (
            <FormFieldSelectContract
              name="contributors"
              mode="multiple"
              label={t('yml_path')}
              placeholder={t(
                'yml_path',
              )}
              filter={{ active: true }}
              optional
              disabled={loading || !canUpdateContributors}
            />
          )}
          {isCreating && (
            <FormFieldGroup
              name="tasks"
              label={t('yml_path')}
              optional
            >
              <TasksForm name="tasks" tasks={formProps.values.tasks} />
            </FormFieldGroup>
          )}
          <Layout mt="px40">{children(formProps)}</Layout>
        </Fragment>
      )}
    </Form>
  );
};

export default MyForm;

您需要创建功能组件来分离重复的部分,以便您可以将其用作主组件中的元素,也可以重复使用它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM