一、引言
之前学习了Redux,然后发现有Redux的地方几乎都少不了react-redux这个库,它可以说是建立了React组件和Redux之间的桥梁。所以我特地学习了react-redux,觉得很有必要记录一下。
二、为什么需要使用react-redux
如果不用react-redux的话,我们想在React组件中使用Redux,就不得不引入store,使用store.dispatch(action)去分派action,间接地改变状态。乍一看,这样没什么问题啊,是的,如果只是一个组件的话这样确实没什么问题。但是,试想,如果该组件的子组件也想用名为xxx的state的话,岂不是又得引入store,然后又是像父组件那样一套流程下来,这样确实生效,但是显得代码非常的臃肿,可维护性很差,因为做了很多重复的工作。但是如果我们引入一个容器组件,这个组件可以使用props的方式将state和dispatch传递给子组件,这样就省去了很多重复的工作。这时,react-redux库就发挥作用了,建立起React和store的桥梁,可以将store和dispatch映射给props,这样React的组件就可以通过props将state和dispatch向下传递;或者可以使用Provider提供一个context,将store无限制向下传递给子孙组件。
三、react-redux做了什么
先说一下react-redux做了什么:
- 提供Subscrption类,实现订阅更新的逻辑
- 提供Provider,将store传入Provider,便于下层组件从context或者props中获取store;并订阅store的变化,便于在store变化的时候执行被订阅到react-redux内的更新函数
- 提供selector,负责将获取store中的state和dispacth一些action的函数(或者直接就是dispatch)或者组件自己的props,从中选择出组件需要的值,作为selector的返回值
提供connect高阶组件,主要做了两件事:
- 执行selector,获取到要注入到组件中的值,将它们注入到组件的props
- 订阅props的变化,负责在props变化的时候更新组件
四、react-redux是怎么做到的
当我了解到react-redux的大致功能之后,我脑海里立马产生了三个疑问,分别是:
- Provider是如何将store放入context中的?
- 如何将store中的state和dispatch(或调用dispatch的函数)注入组件中的props的?
- React-Redux如何做到当store变化,被connect的组件也会更新的?(如何监测store的变化?)
五、解决以上提出的问题
1.Provider是怎么把store放入context中的?
先来看Provider.js的源码:
function Provider({ store, context, children }) {
const contextValue = useMemo(() => {
//声明一个Subscription实例。订阅,监听state变化来执行listener,都由实例来完成
const subscription = new Subscription(store)
//绑定监听,当state变化时,通知订阅者更新页面,实际上也就是在connect过程中被订阅到react-redux的subscription对象上的更新函数
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription
}
}, [store])
//获取当前的store的state,作为上一次的state,将会在组件挂载完毕后,
//与store新的state比较,不一致的话更新Provider组件
const previousState = useMemo(() => store.getState(), [store])
useEffect(() => {
//这会在组件渲染之后执行,所以这个时候contextValue已经返回
//在组件挂载完毕后,订阅更新。
const { subscription } = contextValue //contextValue = { store, subscription }
//这里先理解为最开始的时候需要订阅更新函数,便于在状态变化的时候执行更新函数,相当于是注册了一个监听,在监听state的变化。
subscription.trySubscribe()
//如果前后的store中的state有变化,那么就去更新Provider组件
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
}, [contextValue, previousState])
const Context = context || ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
if (process.env.NODE_ENV !== 'production') {
//propTypes仅在开发模式下进行检查
Provider.propTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}),
context: PropTypes.object,
children: PropTypes.any
}
}
export default Provider
所以结合代码看这个问题:Provider是怎么把store放入context中的,很好理解。
Provider最主要的功能是从props中获取我们传入的store,并将store作为context的其中一个值,向下层组件下发。
但是,一旦store变化,Provider要有所反应,以此保证将始终将最新的store放入context中。所以这里要用订阅来实现更新。自然引出Subscription类,通过该类的实例,将onStateChange监听到一个可更新UI的事件this.notifySubscribers上:
subscription.onStateChange = this.notifySubscribers
组件挂载完成后,去订阅更新,至于这里订阅的是什么,要看Subscription的实现。这里先给出结论:本质上订阅的是onStateChange,实现订阅的函数是:Subscription类之内的trySubscribe。
this.state.subscription.trySubscribe()
再接着,如果前后的state不一样,那么就去通知订阅者更新,onStateChange就会执行,Provider组件就会执行下层组件订阅到react-redux的更新函数。当Provider更新完成(componentDidUpdate),会去比较一下前后的store是否相同,如果不同,那么用新的store作为context的值,并且取消订阅,重新订阅一个新的Subscription实例。保证用的数据都是最新的。
//如果前后的store中的state有变化,那么就去更新Provider组件
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
//未完成...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。