簡體   English   中英

防止重新渲染功能道具更新

[英]Prevent rerender on function prop update

我有一個包含多層子組件的表單。 表單的狀態保持在最高級別,我將函數作為道具傳遞以更新頂級。 唯一的問題是當表單變得非常大(您可以動態添加問題)時,每個組件都會在其中一個更新時重新加載。 這是我的代碼的簡化版本(或代碼和框: 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 和 useReducer,我想出了這個解決方案: https : //codesandbox.io/s/myrjqrjm18

請注意控制台日志如何僅顯示已更改組件的重新渲染。

  1. 對功能組件使用 React 特性React.memo 以防止在 props 未更改時重新渲染,類似於類組件的 PureComponent。
  2. 當你像這樣傳遞回調時:
<Section
    ...
    updateSection={value => updateSection(idx, value)}
/>

每次父組件重新渲染時,您的組件Section都會重新渲染,即使其他 props 沒有更改並且您使用React.memo 因為每次父組件渲染時,您的回調都會重新創建。 您應該將回調包裝在useCallback鈎子中。

  1. 如果您需要存儲像initialForm這樣的復雜對象,使用useState不是一個好的決定。 最好使用useReducer

在這里你可以看到工作解決方案: https : //codesandbox.io/s/o10p05m2vz

我建議使用生命周期方法來防止重新渲染,在您可以使用的 react hooks 示例中,useEffect。 在上下文中集中你的狀態並使用 useContext 鈎子也可能會有所幫助。

通過一個復雜的表單來解決這個問題,我實施的技巧是在組件的輸入元素上使用onBlur={updateFormState}來觸發將表單數據從組件提升到父表單,通過一個作為道具傳遞給組件的函數。

為了更新組件的輸入元素,我使用了組件內的狀態onChange={handleInput} ,然后當輸入(或組件作為一個整體,如果組件中有多個輸入字段)時,該組件狀態通過提升函數傳遞) 失去焦點。

這有點hacky,並且由於某種原因可能是錯誤的,但它在我的機器上工作。 ;-)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM