防止在功能道具更新时重新呈现

时间:2019-05-10 19:05:15

标签: javascript reactjs react-hooks

我有一个包含多层子组件的表单。表单的状态保持在最高级别,而我向下传递作为更新顶层的道具的函数。唯一的问题是,当表单变得很大(您可以动态添加问题)时,每个单个组件都会在其中一个更新时重新加载。这是我的代码的简化版本(或codeandbox: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,但是我不知道如何使它工作。问题是传递函数以更新其父级。我无法弄清楚如何在每次表单更新时不对其进行更新。

我无法在任何地方在线找到解决方案。也许我在寻找错误的东西。感谢您提供的任何帮助!

解决方案

使用Andrii-Golubenko的答案和本文React Optimizations with React.memo, useCallback, and useReducer,我提出了以下解决方案: https://codesandbox.io/s/myrjqrjm18

请注意,控制台日志仅显示重新渲染已更改的组件的方式。

3 个答案:

答案 0 :(得分:3)

  1. 如果属性未更改,则对组件to prevent re-render使用React功能df %>% group_by(location) %>% distinct(value, .keep_all = TRUE) %>% filter(dense_rank(value) == 2) ,类似于对类组件使用PureComponent。
  2. 当您这样传递回调时:
React.memo

您的组件<Section ... updateSection={value => updateSection(idx, value)} /> 每次在父组件重新渲染时都会重新渲染,即使其他道具没有更改并且您使用Section。因为您的回调将在父组件每次渲染时重新创建。

  1. 如果您需要存储React.memo之类的复杂对象,那么使用useState并不是一个明智的选择。最好使用initialForm;

在这里您可以看到有效的解决方案:https://codesandbox.io/s/o10p05m2vz

答案 1 :(得分:0)

我建议使用生命周期方法来防止重新渲染,在您可以使用的react hooks示例中,useEffect。同样,在上下文中集中您的状态并使用useContext挂钩也可能会有所帮助。

答案 2 :(得分:0)

为解决此问题而采用了复杂的表单,我实现的技巧是在组件的输入元素上使用onBlur={updateFormState},以通过作为道具传递给函数的函数触发将表单数据从组件提升到父表单。零件。

为了更新组件的输入元素,我使用了onChange={handleInput}并使用了组件内部的状态,然后在输入(或者整个组件,如果有多个输入字段)时通过提升功能将组件状态传递给了组件在组件中)失去了焦点。

这有点hacky,出于某种原因可能是错误的,但是它在我的机器上可以使用。 ;-)

相关问题