redux
普通搭配使用redux, 在组件內获取redux数据, 需要用redux的方法, 比如用store的一些方法, getstate获取数据, dispatch触发方法等, 但是这样做弊端
1. react和redux耦合度太高, 在组件中到处看到redux api
2. 将redux的store数据先当成根组件的属性值, 然后通过props层层传递到其他的组件, 进而其他组件拿到对应需要的store, 这样的数据流转过于心累
react-redux
这个第三方库, 提供了connect方法和provide容器组件, connect会把react和redux连接起来, provider容器组件相关知识
useSelector
hooks出现后,react-redux提供了useSelector等hooks,作为可以替代connect()高阶组件方式的一种选择。这些hooks可以让我们订阅Redux store和dispatch actions,而不用connect()来包裹我们的components。下面是使用hooks实现的方式:
和connect()方式相比,hooks方式有什么不同呢?
1.更少的样板代码。useSelector()使我们不再需要区分UI组件和container组件。
2.useSelector用于从Redux存储的state中提取值并订阅该state。这基本上类似于在hooks中实现的mapStateToProps函数,但有一些小的差异:
- 不再提供ownProps API,并且应该使用useCallback或useMemo来通过自定义逻辑获取它们
- useSelector默认使用 === 严格相等来检查,而不是浅比较
- 需要考虑使用reselect等记忆选择器来提高性能
3.connect()只有在props改变时才会重新渲染,hooks方式也可以通过使用React.memo来实现同样的效果。
function CounterUseSelector({ allowValueChange }) {
const count = useSelector(selectCount)
const dispatch = useDispatch()
// ...
}
export default React.memo(CounterUseSelector)
4.可能会出现'stale props' 和 'zombie child' 的问题
在version 4 之前,一个使用了mapState的list item,如果刚刚被移除,可能会引起错误。在version 7,React Redux使用改写部分内部的React Context来解决这个问题,但对于hooks来说,没有重新渲染context provider的方式,所以'stale props' 和 'zombie child' 的问题可能会重新出现。
具体来说,以下情况会引起'stale props':
- 选择器依赖于组件的props来提取数据
- 某个action会引起父组件的重新渲染并把新的props传递给该组件
- 但是该组件的选择器函数会在该组件使用新的props重新渲染前执行
由于依赖于props和store state,这种情况可能会拿到错误的数据,甚至会抛出错误。
以下情况会引起'zombie child':
- 第一次挂载多个嵌套的connected component,导致子组件先于父组件订阅store
- dispatch了一个从store中删除数据的action,例如 todo item
- 父组件会停止渲染子组件
- 然而,因为子组件是先订阅的,subscription会在父组件停止渲染前执行。当基于props读取store中的数据时,这个数据已经不存在了,可能会抛出错误
我们可以采取以下方法来避免这些错误:
- 在选择器中,不要依赖于props来提取数据
- 如果需要依赖props来提取数据,要使代码更健壮。比如不要直接使用 state.todos[props.id].name ,首先先读取 state.todos[props.id] ,确定存在后再读取 todo.name
- 因为 connect 增加了对context provider 的必要的订阅并且在组件重新渲染时延迟计算子组件的订阅,所以把使用useSelector的connected components放在上层会避免这些错误
useSelector 作用
调用此 Hook API 时会在 store 上注册监听器。 当 Store::state 变化时,组件会 checkForUpdates,利用 equalityFn 判断是否进行更新。
缺点
没有对 selector 函数做 memorize 优化
解决方案
使用 reselect 对 selector 做 memorize 处理
reselect作用
对 selector 函数(等效于 mapStateToProps 函数)做 memorize 优化,如果 selector 的入参没有发生变化,则返回上一次执行的缓存。
reselect源码其实很短,主要是实现了一个记忆函数
useSelector源码细节
在使用hooks之前,需要将store注入到组件中
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById(``'root'``)
)
注意:这个Provider是react-redux封装后的Provider,不是Context.Provider,Context.Provider的属性是value。
封装的Provider为:
通过store.subscribe(listener),将checkForUpdate作为listener,当store改变并且选取的state上次不同时,会调用forceRender()重新渲染。
总结
1.useSelector让我们使用react-redux更加方便,可以用来代替connect()的方式。
2.使用方式为 const count = useSelector(selectCount);
3.useSelector本身没有记忆功能,可以用reselect来增加记忆功能,避免重复进行复杂的计算
4.useSelector可能会出现一些预料之外的问题,写代码时应避免出现这些问题