react-window 在滚动父 FixedSIzeList 时停止不必要地渲染子 FixedSizeList 行
react-window stop unnecessary rendering of child FixedSizeList rows when scrolling parent FixedSIzeList
在我的 React 项目中,我使用 react-window
包来呈现嵌套列表。每个父 FixedSizeList
行呈现一个使用另一个 FixedSizeList
的组件。 Parent List 目前不超过 14 行。但子列表最多可包含 2000 行。现在我的问题是,当我尝试滚动父列表时,视口中的所有子列表项似乎都在重新渲染。这对我来说有点问题,因为在我的子列表项中,我使用 d3js
绘制具有过渡效果的条形图。所以这些不必要的重新渲染给人一种整体怪异的感觉 UI。任何人都可以帮助我如何停止这些不必要的渲染。
Here 是 codesandbox link 我的问题的一个非常简单的例子。
请打开控制台日志。初始加载后,最上面的日志应该是这样的:initial console log.
然后如果您清除控制台并滚动父列表,您将看到这样的日志:console log after parent scrolling。在这里你可以看到子列表 0 的子列表项正在重新呈现,这对我来说并不需要。
任何人都可以给我一个可以停止这些重新渲染的解决方案吗?
*P.S。我没有使用备忘录,因为每一行都在自己更新 dom。
编辑
我认为如果父列表停止向子列表传播滚动事件,这个问题就会解决。我尝试在父列表行中添加 event.stopPropagation()
和 event.stopImmediatePropagation()
,但输出与之前相同。
我们可以使用 memo
来避免为同一组 props
不必要地重新渲染组件。并使用 useCallback
来防止重新创建函数,从而确保重新渲染子组件。应用这些,我们可以得到这个解决方案:
import "./styles.css";
import { FixedSizeList as List } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("rendering child list", parentIndex);
const InnerRow = useCallback(({ index, style }) => {
console.log("rendering child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
}, []);
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRow}
</List>
</div>
);
});
const Example = () => {
console.log("rendering parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
虽然上面的代码工作正常,但是如果我们使用react-window
中的areEqual
方法作为react memo
依赖,它可以得到更多的优化。而对于 more 如果我们想在 InnerRow
组件中使用其他 react hooks,我们必须添加一个 InnerRow
的中间件组件。完整示例如下:
import { FixedSizeList as List, areEqual } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("mounting child list", parentIndex);
const data = new Array(15).fill(new Array(500).fill(1));
const InnerRowCallback = useCallback(
({ index, style }) => {
return <InnerRow index={index} style={style} />;
},
[data]
);
const InnerRow = ({ index, style }) => {
console.log("mounting child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
};
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRowCallback}
</List>
</div>
);
}, areEqual);
const Example = () => {
console.log("mounting parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
这里我将 data
数组作为 useCallBack 依赖项传递,因为如果 data
发生更改,我想重新渲染 InnerRow
组件。
在我的 React 项目中,我使用 react-window
包来呈现嵌套列表。每个父 FixedSizeList
行呈现一个使用另一个 FixedSizeList
的组件。 Parent List 目前不超过 14 行。但子列表最多可包含 2000 行。现在我的问题是,当我尝试滚动父列表时,视口中的所有子列表项似乎都在重新渲染。这对我来说有点问题,因为在我的子列表项中,我使用 d3js
绘制具有过渡效果的条形图。所以这些不必要的重新渲染给人一种整体怪异的感觉 UI。任何人都可以帮助我如何停止这些不必要的渲染。
Here 是 codesandbox link 我的问题的一个非常简单的例子。 请打开控制台日志。初始加载后,最上面的日志应该是这样的:initial console log.
然后如果您清除控制台并滚动父列表,您将看到这样的日志:console log after parent scrolling。在这里你可以看到子列表 0 的子列表项正在重新呈现,这对我来说并不需要。
任何人都可以给我一个可以停止这些重新渲染的解决方案吗?
*P.S。我没有使用备忘录,因为每一行都在自己更新 dom。
编辑
我认为如果父列表停止向子列表传播滚动事件,这个问题就会解决。我尝试在父列表行中添加 event.stopPropagation()
和 event.stopImmediatePropagation()
,但输出与之前相同。
我们可以使用 memo
来避免为同一组 props
不必要地重新渲染组件。并使用 useCallback
来防止重新创建函数,从而确保重新渲染子组件。应用这些,我们可以得到这个解决方案:
import "./styles.css";
import { FixedSizeList as List } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("rendering child list", parentIndex);
const InnerRow = useCallback(({ index, style }) => {
console.log("rendering child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
}, []);
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRow}
</List>
</div>
);
});
const Example = () => {
console.log("rendering parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
虽然上面的代码工作正常,但是如果我们使用react-window
中的areEqual
方法作为react memo
依赖,它可以得到更多的优化。而对于 more 如果我们想在 InnerRow
组件中使用其他 react hooks,我们必须添加一个 InnerRow
的中间件组件。完整示例如下:
import { FixedSizeList as List, areEqual } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("mounting child list", parentIndex);
const data = new Array(15).fill(new Array(500).fill(1));
const InnerRowCallback = useCallback(
({ index, style }) => {
return <InnerRow index={index} style={style} />;
},
[data]
);
const InnerRow = ({ index, style }) => {
console.log("mounting child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
};
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRowCallback}
</List>
</div>
);
}, areEqual);
const Example = () => {
console.log("mounting parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
这里我将 data
数组作为 useCallBack 依赖项传递,因为如果 data
发生更改,我想重新渲染 InnerRow
组件。