避免使用 react-redux 重新渲染大量项目
Avoid re-rendering a big list of items with react-redux
我正在为我的应用程序使用 redux 和 react 和 typescript。我正在处理在我的应用程序的不同位置使用的许多项目。我的状态是这样的:
{
items: {42: {}, 53: {}, ... }, //A large dictionary of items
itemPage1: {
itemsId: [ 42, 34, 4 ],
...
},
itemPage2: { ...
},
...
}
用户可以修改items
调度一些动作的一些属性。发生这种情况时,我需要重新绘制每个页面中已修改的组件。问题是我的项目很大,我无法承受在每次小修改时重新绘制所有项目。我想知道这种方法是否有效:
- 我有一个拳头组件
<ItemPage1>
,它连接到商店以获取存储在 itemPage1
下的树中的所有状态,例如项目列表 id:itemsId
.
- 在
<ItemPage1>
中,我遍历 itemsId
属性 以生成多个 FilterItem
组件:itemsId.map( itemId => return <FilterItem id=itemId>);
最后每个 Item
使用 ownProps
连接以获得状态的正确部分:
const mapStateToItemProps = (state, ownProps) => {
return {
item: state.items[ownProps.id],
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
你能证实或反驳如果我更新id 42的项目,那么只有这个项目会被重新渲染吗?
好的,我找到了这个讨论:https://github.com/reactjs/redux/issues/1303
最下面写的很清楚(来自多位主角):
[...] react-redux 会处理这个问题。它允许您指定您关心的状态的特定部分,并在相关部分未更改时注意避免更新 React 组件。
[...] 只是想完全了解这里的幕后情况,所以如果 Redux store 更新但一个特定的组件状态没有改变,Redux 不会触发该组件的 forceUpdate() 方法? [...]
由 React-Redux 的 connect() 函数生成的包装器组件会进行多项检查,以尽量减少实际组件必须重新渲染的次数。这包括 shouldComponentUpdate 的默认实现,并对进入组件的道具进行浅层相等检查(包括从 mapStateToProps 返回的内容)。所以是的,作为一般规则,连接的组件只会在它从状态中提取的值发生变化时才重新渲染。
所以我相信我的实现很好,它不会重新呈现所有项目,因为只有一个项目的属性会被修改。
渲染大列表时需要考虑以下几点:
- 减少需要渲染的 DOM 元素总数(通过不渲染在屏幕上实际不可见的项目,也称为虚拟化)
- 不要重新呈现未更改的项目
基本上,您想要避免的是当用户编辑一行时完全重新呈现您的列表(或您的页面)。这可以完全按照您的方式实现,即:通过仅将需要呈现的项目的 ID 传递给列表容器,并使用 ownProps
将这些 ID 映射到 connect
每个组件.如果您有转储 <Item/>
组件,您的 <ItemPage/>
组件将创建连接的 connect(<Item/>)
组件。
这将起作用,如果您在 <Item/>
组件 class 中放置一个 console.log('item rendered')
,您会注意到只有一个调用。
BUT(这是一个很大的但是),使用 react-redux
时不明显的是所有连接的组件都依赖于它们的 ownProps
如果状态的任何部分发生变化,将总是重新渲染。在您的情况下,即使 <Item/>
组件不会重新渲染,它们的包装组件 connect(Item)
也会!如果您有几十个项目,如果需要快速分派操作(例如输入输入时),您可能会遇到一些延迟。如何避免这种情况?使用工厂函数使用 ownProps
作为初始道具:
const mapStateToItemProps = (_, initialProps) => (state) => {
return {
item: state.items[initialProps.id], // we're not relying on the second parameters "ownProps" here, so the wrapper component will not rerender
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
我建议你看看。
您可能也对这些优秀的幻灯片感兴趣:Big List High Performance React & Redux
最后,您应该明确地查看 react-virtualized 以执行列表的虚拟化(即仅显示用户实际可以看到的项目)。
我正在为我的应用程序使用 redux 和 react 和 typescript。我正在处理在我的应用程序的不同位置使用的许多项目。我的状态是这样的:
{
items: {42: {}, 53: {}, ... }, //A large dictionary of items
itemPage1: {
itemsId: [ 42, 34, 4 ],
...
},
itemPage2: { ...
},
...
}
用户可以修改items
调度一些动作的一些属性。发生这种情况时,我需要重新绘制每个页面中已修改的组件。问题是我的项目很大,我无法承受在每次小修改时重新绘制所有项目。我想知道这种方法是否有效:
- 我有一个拳头组件
<ItemPage1>
,它连接到商店以获取存储在itemPage1
下的树中的所有状态,例如项目列表 id:itemsId
. - 在
<ItemPage1>
中,我遍历itemsId
属性 以生成多个FilterItem
组件:itemsId.map( itemId => return <FilterItem id=itemId>);
最后每个
Item
使用ownProps
连接以获得状态的正确部分:const mapStateToItemProps = (state, ownProps) => { return { item: state.items[ownProps.id], } } const mapDispatchToItemProps = (dispatch, ownProps) => { return null; } const FilterItem = connect( mapStateToItemProps, mapDispatchToItemProps )(Item)
你能证实或反驳如果我更新id 42的项目,那么只有这个项目会被重新渲染吗?
好的,我找到了这个讨论:https://github.com/reactjs/redux/issues/1303
最下面写的很清楚(来自多位主角):
[...] react-redux 会处理这个问题。它允许您指定您关心的状态的特定部分,并在相关部分未更改时注意避免更新 React 组件。
[...] 只是想完全了解这里的幕后情况,所以如果 Redux store 更新但一个特定的组件状态没有改变,Redux 不会触发该组件的 forceUpdate() 方法? [...]
由 React-Redux 的 connect() 函数生成的包装器组件会进行多项检查,以尽量减少实际组件必须重新渲染的次数。这包括 shouldComponentUpdate 的默认实现,并对进入组件的道具进行浅层相等检查(包括从 mapStateToProps 返回的内容)。所以是的,作为一般规则,连接的组件只会在它从状态中提取的值发生变化时才重新渲染。
所以我相信我的实现很好,它不会重新呈现所有项目,因为只有一个项目的属性会被修改。
渲染大列表时需要考虑以下几点:
- 减少需要渲染的 DOM 元素总数(通过不渲染在屏幕上实际不可见的项目,也称为虚拟化)
- 不要重新呈现未更改的项目
基本上,您想要避免的是当用户编辑一行时完全重新呈现您的列表(或您的页面)。这可以完全按照您的方式实现,即:通过仅将需要呈现的项目的 ID 传递给列表容器,并使用 ownProps
将这些 ID 映射到 connect
每个组件.如果您有转储 <Item/>
组件,您的 <ItemPage/>
组件将创建连接的 connect(<Item/>)
组件。
这将起作用,如果您在 <Item/>
组件 class 中放置一个 console.log('item rendered')
,您会注意到只有一个调用。
BUT(这是一个很大的但是),使用 react-redux
时不明显的是所有连接的组件都依赖于它们的 ownProps
如果状态的任何部分发生变化,将总是重新渲染。在您的情况下,即使 <Item/>
组件不会重新渲染,它们的包装组件 connect(Item)
也会!如果您有几十个项目,如果需要快速分派操作(例如输入输入时),您可能会遇到一些延迟。如何避免这种情况?使用工厂函数使用 ownProps
作为初始道具:
const mapStateToItemProps = (_, initialProps) => (state) => {
return {
item: state.items[initialProps.id], // we're not relying on the second parameters "ownProps" here, so the wrapper component will not rerender
}
}
const mapDispatchToItemProps = (dispatch, ownProps) => {
return null;
}
const FilterItem = connect(
mapStateToItemProps,
mapDispatchToItemProps
)(Item)
我建议你看看
您可能也对这些优秀的幻灯片感兴趣:Big List High Performance React & Redux
最后,您应该明确地查看 react-virtualized 以执行列表的虚拟化(即仅显示用户实际可以看到的项目)。