当存在大状态但只有一个变化时,如何减少无用的 React 渲染?

How to mitigate useless React renders when large state is present but only one changes?

我有一个搜索查询组件。用户键入查询,单击 运行 并执行它。然而,由于状态的组织方式,它会导致过多的重新渲染。以下是它的简化。这种代码组织对我来说很自然。 QueryEditor 通常是一个复杂的编辑器,因此它存储自己的值。查询结果类似。我在每次更改时都得到了它的值,更新了父组件中的查询。然而,这会导致父组件中的所有 HTML 元素重新呈现,即使它们的状态没有改变。

首先,对概念不是很熟悉;

Link 如果你愿意:https://codesandbox.io/s/purple-architecture-5kpx5?file=/src/App.tsx 你可以清楚地看到 React DevTools 对性能的影响,并看到渲染时间随着元素数量的增加而增加,即使元素没有改变但查询文本变化。

function App() {
  console.log("Re-render?");
  
  const [name, setName] = useState("mustafa");
  const [query, setQuery] = useState("");
  const [data, setData] = useState<number[]>([]);

  const runQuery = () => {
    console.log("using query...", query);
    setData([...Array(data.length + 1000).keys()]);
  };

  return (
    <div>
      <h1>Hello {name}</h1>

      <p>Please enter your query</p>
      <QueryEditor
        onChange={(e) => {
          setQuery(e.target.value);
        }}
      />

      <br />
      <button onClick={runQuery}>Run Query</button>
      <hr />

      <h4>Results</h4>
      {data.length > 0 && <p>There are {data.length} results </p>}
      {data.length > 0 && <DataResults data={data} />}
    </div>
  );
}

interface EditorProps {
  onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
}

function QueryEditor({ onChange }: EditorProps) {
  return <textarea onChange={onChange} />;
}

function DataResults({ data }: { data: number[] }) {
  return (
    <div>
      <ul>
        {data.map((a) => (
          <li key={a}>Item {a}</li>
        ))}
      </ul>
    </div>
  );
}


您无法更改 parent 对 re-rendering 的行为。这就是 React 的工作方式,当 children 必须 re-render,然后 parent 必须 re-render child,因此被再次调用。 Have a look at the docs to understand the logic behind re-rendering.

在这种情况下,您可以做的是将拥有大量数据的兄弟姐妹记忆化。

所以如果我这样做:

const DataResultsMemoized = React.memo(DataResults);

然后在parent中使用它:

      {data.length > 0 && <DataResultsMemoized data={data} />}

那么你的兄弟姐妹就不会 re-render 每次在文本框中输入内容。

Here's code sandbox example,检查children里面的console.log在你输入

的时候不会再走

useState 将始终导致 re-rendering。当您在文本区域中键入时,您会更新导致重新呈现的查询(这是 useState 的正常行为)。 在您的情况下,在单击 运行 查询按钮之前,您不会使用查询。 您可以将 const [query, setQuery] = useState(""); 更改为 const query = useRef("")。然后你的 QueryEditor onChange 函数变成

{(e) => {
  query.current = (e.target.value);
}}

这样你就不应该重新渲染所有这些。 PS : 当你 运行 查询时,你必须使用 query.current 而不是仅仅查询