Предотвратить повторную визуализацию при обновлении функции - PullRequest
6 голосов
/ 10 мая 2019

У меня есть форма с несколькими слоями дочерних компонентов.Состояние формы поддерживается на самом высоком уровне, и я передаю функции в качестве реквизита для обновления верхнего уровня.Единственная проблема с этим - когда форма становится очень большой (вы можете динамически добавлять вопросы), каждый отдельный компонент перезагружается при обновлении одного из них.Вот упрощенная версия моего кода (или кодов и коробки: https://codesandbox.io/s/636xwz3rr):

const App = () => {
  return <Form />;
}

const initialForm = {
  id: 1,
  sections: [
    {
      ordinal: 1,
      name: "Section Number One",
      questions: [
        { ordinal: 1, text: "Who?", response: "" },
        { ordinal: 2, text: "What?", response: "" },
        { ordinal: 3, text: "Where?", response: "" }
      ]
    },
    {
      ordinal: 2,
      name: "Numero dos",
      questions: [
        { ordinal: 1, text: "Who?", response: "" },
        { ordinal: 2, text: "What?", response: "" },
        { ordinal: 3, text: "Where?", response: "" }
      ]
    }
  ]
};

const Form = () => {
  const [form, setForm] = useState(initialForm);

  const updateSection = (idx, value) => {
    const { sections } = form;
    sections[idx] = value;
    setForm({ ...form, sections });
  };

  return (
    <>
      {form.sections.map((section, idx) => (
        <Section
          key={section.ordinal}
          section={section}
          updateSection={value => updateSection(idx, value)}
        />
      ))}
    </>
  );
};

const Section = props => {
  const { section, updateSection } = props;

  const updateQuestion = (idx, value) => {
    const { questions } = section;
    questions[idx] = value;
    updateSection({ ...section, questions });
  };

  console.log(`Rendered section "${section.name}"`);

  return (
    <>
      <div style={{ fontSize: 18, fontWeight: "bold", margin: "24px 0" }}>
        Section name:
        <input
          type="text"
          value={section.name}
          onChange={e => updateSection({ ...section, name: e.target.value })}
        />
      </div>
      <div style={{ marginLeft: 36 }}>
        {section.questions.map((question, idx) => (
          <Question
            key={question.ordinal}
            question={question}
            updateQuestion={v => updateQuestion(idx, v)}
          />
        ))}
      </div>
    </>
  );
};

const Question = props => {
  const { question, updateQuestion } = props;

  console.log(`Rendered question #${question.ordinal}`);

  return (
    <>
      <div>{question.text}</div>
      <input
        type="text"
        value={question.response}
        onChange={e =>
          updateQuestion({ ...question, response: e.target.value })
        }
      />
    </>
  );
};

Я пытался использовать useMemo и useCallback, но я не могу понять, как заставить его работать. Проблема проходитвниз функцию, чтобы обновить его родитель. Я не могу понять, как это сделать, не обновляя его каждый раз, когда обновляется форма.

Я не могу найти решение онлайн в любом месте. Может быть, я ищуНеправильно. Спасибо за любую помощь, которую вы можете предложить!

Решение

Использование ответа Андрей-Голубенко и этой статьи Реакция оптимизации с React.memo, useCallback и useReducer Мне удалось придумать это решение: https://codesandbox.io/s/myrjqrjm18

Обратите внимание, что журнал консоли показывает только повторную визуализацию измененных компонентов.

Ответы [ 2 ]

3 голосов
/ 11 мая 2019
  1. Используйте функцию React React.memo для функциональных компонентов , чтобы предотвратить повторную визуализацию , если реквизиты не изменены, аналогично PureComponent для компонентов класса.
  2. Когда вы передаете обратный вызов подобным образом:
<Section
    ...
    updateSection={value => updateSection(idx, value)}
/>

ваш компонент Section будет перерисовываться каждый раз при перерисовке родительского компонента, даже если другие реквизиты не изменены, и вы используете React.memo.Потому что ваш обратный вызов будет пересоздавать каждый раз, когда рендерит родительский компонент.

Использование useState не является хорошим решением, если вам нужно хранить сложные объекты, такие как initialForm.Лучше использовать useReducer;

Здесь вы можете увидеть рабочее решение: https://codesandbox.io/s/o10p05m2vz

0 голосов
/ 10 мая 2019

Я бы предложил использовать методы жизненного цикла для предотвращения повторного рендеринга. В примере с перехватчиками реагирования вы можете использовать useEffect. Также может помочь централизация вашего состояния в контексте и использование ловушки useContext.

...