当存在大状态但只有一个变化时,如何减少无用的 React 渲染?
How to mitigate useless React renders when large state is present but only one changes?
我有一个搜索查询组件。用户键入查询,单击 运行 并执行它。然而,由于状态的组织方式,它会导致过多的重新渲染。以下是它的简化。这种代码组织对我来说很自然。 QueryEditor 通常是一个复杂的编辑器,因此它存储自己的值。查询结果类似。我在每次更改时都得到了它的值,更新了父组件中的查询。然而,这会导致父组件中的所有 HTML 元素重新呈现,即使它们的状态没有改变。
首先,对概念不是很熟悉;
这是预期的行为吗?我所做的可能并不理想,但导致每个 DOM 元素重新渲染,因为父状态改变对我来说是不必要的。或者我做错了什么。
如何改进?显然需要改进的不是将每个更改都作为回调获取,但我没有找到从父组件调用子组件方法 getValue() 的好方法。
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
而不是仅仅查询
我有一个搜索查询组件。用户键入查询,单击 运行 并执行它。然而,由于状态的组织方式,它会导致过多的重新渲染。以下是它的简化。这种代码组织对我来说很自然。 QueryEditor 通常是一个复杂的编辑器,因此它存储自己的值。查询结果类似。我在每次更改时都得到了它的值,更新了父组件中的查询。然而,这会导致父组件中的所有 HTML 元素重新呈现,即使它们的状态没有改变。
首先,对概念不是很熟悉;
这是预期的行为吗?我所做的可能并不理想,但导致每个 DOM 元素重新渲染,因为父状态改变对我来说是不必要的。或者我做错了什么。
如何改进?显然需要改进的不是将每个更改都作为回调获取,但我没有找到从父组件调用子组件方法 getValue() 的好方法。
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
而不是仅仅查询