Шаблон Вопроса
Проблема
Привет, ребята!
Недавно я заметил несколько очень логичных c -интенсивных компонентов реакции, создаваемых в приложении I работать, и у этих компонентов обычно есть десятки строк, которые я называю «setup logi c» (переменные, которые необходимо установить перед тем, как я рендерил свой компонент). Вот некоторые из этих вещей:
- Отправка запроса в службу перевода для получения необходимых переведенных строк для этого компонента
- Создание производных данных из состояний, таких как:
- показывать или нет компонент с данным флагом функции из API
- Процент индикатора выполнения с учетом начального и конечного значения, полученного API
- Получение currentUser
- Получение фирменных цветов и размеров шрифта по умолчанию
- Мутации / запросы, которые будут выполняться, когда пользователь нажимает кнопку
- Отображение всей этой информации в стиль компоненты и дочерние компоненты
Возникает вопрос: «Мы слишком много делаем в этих компонентах?»
Несомненно, да, но как именно мы разбиваем это на части? Где мы рисуем линию?
Ах, и на голову, это наша установка в настоящее время:
Информация о стеке:
Внешний интерфейс: Реагировать
API: GraphQL
Возможное решение
Отделение лога установки c (получателей, преобразователей и запросов) от лога представления c (JSX).
Вопрос 1. Если существует компонент, который несет полную ответственность за обслуживание компонента презентации с его логами c?
Вопрос 2. Должен ли это вообще быть компонентом или эта логика c должна обслуживаться API-интерфейсом GraphQL? Насколько связан GraphQL с компонентом React?
Пример кода
Это пример, иллюстрирующий проблему:
// 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;