You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionTodoItem({ id }){constselected=useSelector(state=>state.selectedTodoId===id);consttodo=useSelector(state=>state.todos.find(item=>item.id===id));if(!todo){returnnull;}return<divclassName={classNames('todo',{'todo--selected': selected,})}>{todo.title}</div>;}
从 Redux Store 中取值
关于这个主题,Redux 官网的专业说法是
Computing Derived Data
,即计算衍生数据
。本章节将会探讨如何在项目中处理好这个主题。选择器
selector
从 Redux Store 中取值的方法,我们称之为选择器。形如下面的函数:
接收 Redux 状态,从状态中取值,可能会有二次计算,然后返回取值、计算结果。
稍微复杂一点的选择器:
再复杂一点的选择器:
在 React 组件中使用选择器
在 React 项目中,可以使用 react-redux 的
useSelector
,结合选择器,从 Redux 状态中取值。partialRight
lodash/pratialRight 函数是用来给指定函数添加上固定的右侧参数。如下所示:
在其他场景使用选择器
选择器只是简单的函数,所以可以在任何场景下当成函数去使用。例如:
组合使用选择器
组合性是一种理想的编程状态。函数恰恰符合这个预期。如上面的例子中,从状态中取
todos
是一个公共的需求,所以我们稍微改造一下方法:上面的实现还是比较简陋的,下面我们使用reselect,再简化一下:
缓存选择器
reselect 除了帮助简化选择器组合,还有一个重要特性,即计算缓存。如:
getTodos
和getFilterText
都是取对象属性操作,执行效率非常高,可以忽略不计。getFilteredTodos
是数组过滤操作,要耗费更多的执行时间,如果数组比较庞大,频繁执行getFilteredTodos
函数可能会成为性能瓶颈。如何解决呢?使用函数计算缓存的技巧即可。我们希望达到一个效果,即:只有当
todos
和filterText
发生了变化,才会再次执行 getFilteredTodos ,获取新的值,否则返回上次执行的结果。刚好上面的写法就实现了此效果。在 form-designer 中的错误做法
form-designer是将从状态中取数据作为React组件的状态这个过程封装成独立的部分,如下所示:
这样的做法本身没有问题,但是却有一个核心的难题:无法组合使用。
比如下面的用法就有很大的问题:
useTodo
hook 有一个重要的性能问题:当任何一个待办事项变化了,使用useTodo
的组件都会重绘。可以看下个章节的分析。如何高效地使用
useSelector
react-redux 从 7.0 开始提供
useSelector
这个从 Redux 状态中提取值的 hook。它的用法是:有时我们可能会这样去用:
第二种用法的问题是,当别的待办事项数据发生了变化,当前 TodoItem 组件也会跟着重绘。这样可能会带来严重的性能问题。
会在下面的章节中分析原因。
避免重绘的特例
有时候,
useSelector
返回的数据已经是最小范围了,但是发现还是组件重绘太频繁,如下面的示例:你会发现,上面的组件会在 redux 状态有任何变化时重绘。问题就出在
useSelector
的返回值每次都是不一样的对象。我们可以通过下面的三种手段解决问题。手段1:拆分
手段2:使用缓存选择器
selectors.tsx:
Demo.tsx:
手段3:使用浅比较函数
useSelector()
会在 Redux 状态发生变化后比较 selector 函数返回值是否与上次返回值相等,它的相等比较用的是Object.is。但是可以指定useSelector()
使用浅比较,如下所示:建议:先采用手段1和手段2,最后没有再好的办法时,才采用手段3。
剖析 useSelector
带着问题看 useSelector 怎么实现的:
先看一个最基本的实现:
这个实现忽略了很多其他细节,实际实现要考虑很多边际问题,这里不展开讨论。
从这个基础实现可以很好地回答上面三个问题:
store
的subscribe
方法添加状态变更监听器,解决问题1。setState
新的值,通知组件重绘,解决问题2。setState
设置相同的值,不会引起组件重绘,解决问题3。在项目中正确使用选择器
选择器放在什么位置?
首先,建议在项目中建立一个
selectors
文件夹,存放选择器,如下:模块:
项目:
测试要求
选择器是项目中重要的代码,里面甚至有可能有一些业务逻辑,单元测试是有必要的。因为选择器是简单的纯函数,所以单元测试也是很简单的。选择器特别适用测试驱动开发。要求大家在平时项目中对这部分采用测试驱动开发。
与 useSelector 结合着使用
与 reselect 结合着使用
性能最后的武器:浅比较
请把这一招作为最后的招数。
参考资料
The text was updated successfully, but these errors were encountered: