使用过redux
的同窗都知道,redux
做为react
公共状态管理工具,配合react-redux
能够很好的管理数据,派发更新,更新视图渲染的做用,那么对于 react-redux
是如何作到根据 state
的改变,而更新组件,促使视图渲染的呢,让咱们一块儿来探讨一下,react-redux
源码的奥妙所在。前端
在正式分析以前咱们不妨来想几个问题:
vue
1 为何要在 root
根组件上使用 react-redux
的 Provider
组件包裹?
2 react-redux
是怎么和 redux
契合,作到 state
改变动新视图的呢?
3 provide
用什么方式存放当前的 redux
的 store
, 又是怎么传递给每个须要管理state
的组件的?
4 connect
是怎么样链接咱们的业务组件,而后传递咱们组件更新函数的呢?
5 connect
是怎么经过第一个参数,来订阅与之对应的 state
的呢?
6 connect
怎么样将 props
,和 redux
的 state
合并的?react
带着这些疑问咱们不妨先看一下 Provider
究竟作了什么?算法
/* provider 组件代码 */
function Provider({ store, context, children }) {
/* 利用useMemo,跟据store变化建立出一个contextValue 包含一个根元素订阅器和当前store */
const contextValue = useMemo(() => {
/* 建立了一个根 Subscription 订阅器 */
const subscription = new Subscription(store)
/* subscription 的 notifyNestedSubs 方法 ,赋值给 onStateChange方法 */
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription
} /* store 改变建立新的contextValue */
}, [store])
/* 获取更新以前的state值 ,函数组件里面的上下文要优先于组件更新渲染 */
const previousState = useMemo(() => store.getState(), [store])
useEffect(() => {
const { subscription } = contextValue
/* 触发trySubscribe方法执行,建立listens */
subscription.trySubscribe() // 发起订阅
if (previousState !== store.getState()) {
/* 组件更新渲染以后,若是此时state发生改变,那么当即触发 subscription.notifyNestedSubs 方法 */
subscription.notifyNestedSubs()
}
/* */
return () => {
subscription.tryUnsubscribe() // 卸载订阅
subscription.onStateChange = null
}
/* contextValue state 改变出发新的 effect */
}, [contextValue, previousState])
const Context = context || ReactReduxContext
/* context 存在用跟元素传进来的context ,若是不存在 createContext建立一个context ,这里的ReactReduxContext就是由createContext建立出的context */
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
复制代码
1 首先建立一个 contextValue
,里面包含一个建立出来的父级 Subscription
(咱们姑且先称之为根级订阅器)和redux
提供的store
。
2 经过react上下文context
把 contextValue
传递给子孙组件。redux
在咱们分析了不是很长的 provider
源码以后,随之一个 Subscription
出现,那么这个 Subscription
由什么做用呢🤔🤔🤔,咱们先来看看在 Provder
里出现的Subscription
方法。小程序
notifyNestedSubs
trySubscribe
tryUnsubscribe
设计模式
在整个 react-redux
执行过程当中 Subscription
做用很是重要,这里方便先透漏一下,他的做用是收集全部被 connect
包裹的组件的更新函数 onstatechange
,而后造成一个 callback
链表,再由父级 Subscription
统一派发执行更新,咱们暂且不关心它是怎么运做的,接下来就是 Subscription
源码 ,咱们重点看一下如上出现的三个方法。前端工程化
/* 发布订阅者模式 */
export default class Subscription {
constructor(store, parentSub) {
this.store = store
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
/* 负责检测是否该组件订阅,而后添加订阅者也就是listener */
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
/* 向listeners发布通知 */
notifyNestedSubs() {
this.listeners.notify()
}
/* 对于 provide onStateChange 就是 notifyNestedSubs 方法,对于 connect 包裹接受更新的组件 ,onStateChange 就是 负责更新组件的函数 。 */
handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange()
}
}
/* 判断有没有开启订阅 */
isSubscribed() {
return Boolean(this.unsubscribe)
}
/* 开启订阅模式 首先判断当前订阅器有没有父级订阅器 , 若是有父级订阅器(就是父级Subscription),把本身的handleChangeWrapper放入到监听者链表中 */
trySubscribe() {
/* parentSub 便是provide value 里面的 Subscription 这里能够理解为 父级元素的 Subscription */
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
/* provider的Subscription是不存在parentSub,因此此时trySubscribe 就会调用 store.subscribe */
: this.store.subscribe(this.handleChangeWrapper)
this.listeners = createListenerCollection()
}
}
/* 取消订阅 */
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
复制代码
看完 Provider
和 Subscription
源码,我来解释一下二者到底有什么关联,首先Provider
建立 Subscription
时候没有第二个参数,就说明provider
中的Subscription
不存在 parentSub
。 那么再调用Provider
组件中useEffect
钩子中trySubscribe
的时候,会触发this.store.subscribe
, subscribe
就是 redux
的 subscribe
,此时真正发起了订阅。数组
subscription.onStateChange = subscription.notifyNestedSubs
复制代码
有此可知,最终state
改变,触发的是notifyNestedSubs
方法。咱们再一次看看这个notifyNestedSubs
。缓存
/* 向listeners发布通知 */
notifyNestedSubs() {
this.listeners.notify()
}
复制代码
最终向当前Subscription
的订阅者们发布 notify
更新。
综上所述咱们总结一下。Subscription
的做用,首先经过 trySubscribe
发起订阅模式,若是存在这父级订阅者,就把本身更新函数handleChangeWrapper
,传递给父级订阅者,而后父级由 addNestedSub
方法将此时的回调函数(更新函数)添加到当前的 listeners
中 。若是没有父级元素(Provider
的状况),则将此回调函数放在store.subscribe
中,handleChangeWrapper
函数中onStateChange
,就是 Provider
中 Subscription
的 notifyNestedSubs
方法,而 notifyNestedSubs
方法会通知listens
的 notify
方法来触发更新。这里透漏一下,子代Subscription
会把更新自身handleChangeWrapper
传递给parentSub
,来统一通知connect
组件更新。
这里咱们弄明白一个问题
react-redux
更新组件也是用了 store.subscribe
并且 store.subscribe
只用在了 Provider
的 Subscription
中 (没有 parentsub
)
大体模型就是
state
更改 -> store.subscribe
-> 触发 provider
的 Subscription
的 handleChangeWrapper
也就是 notifyNestedSubs
-> 通知 listeners.notify()
-> 通知每一个被 connect
容器组件的更新 -> callback
执行 -> 触发子组件Subscription
的 handleChangeWrapper ->触发子 onstatechange
(能够提早透漏一下,onstatechange
保存了更新组件的函数)。
前边的内容提到了**createListenerCollection
,listeners
**,可是他具体有什么做用咱们接下来一块儿看一下。
function createListenerCollection() {
/* batch 由getBatch获得的 unstable_batchedUpdates 方法 */
const batch = getBatch()
let first = null
let last = null
return {
/* 清除当前listeners的全部listener */
clear() {
first = null
last = null
},
/* 派发更新 */
notify() {
batch(() => {
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
},
/* 获取listeners的全部listener */
get() {
let listeners = []
let listener = first
while (listener) {
listeners.push(listener)
listener = listener.next
}
return listeners
},
/* 接收订阅,将当前的callback(handleChangeWrapper)存到当前的链表中 */
subscribe(callback) {
let isSubscribed = true
let listener = (last = {
callback,
next: null,
prev: last
})
if (listener.prev) {
listener.prev.next = listener
} else {
first = listener
}
/* 取消当前 handleChangeWrapper 的订阅*/
return function unsubscribe() {
if (!isSubscribed || first === null) return
isSubscribed = false
if (listener.next) {
listener.next.prev = listener.prev
} else {
last = listener.prev
}
if (listener.prev) {
listener.prev.next = listener.next
} else {
first = listener.next
}
}
}
}
}
复制代码
batch
import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
setBatch(batch)
复制代码
咱们能够得出结论 createListenerCollection
能够产生一个 listeners
。 listeners
的做用。
1收集订阅: 以链表的形式收集对应的 listeners
(每个Subscription
) 的handleChangeWrapper
函数。
2派发更新:, 经过 batch
方法( react-dom
中的 unstable_batchedUpdates
) 来进行批量更新。
舒适提示: React
的 unstable_batchedUpdate()
API
容许将一次事件循环中的全部 React 更新都一块儿批量处理到一个渲染过程当中。
🤔到这里咱们明白了:
1 react-redux
中的 provider
做用 ,经过 react
的 context
传递 subscription
和 redux
中的store
,而且创建了一个最顶部根 Subscription
。
2 Subscription
的做用:起到发布订阅做用,一方面订阅 connect
包裹组件的更新函数,一方面经过 store.subscribe
统一派发更新。
3 Subscription
若是存在这父级的状况,会把自身的更新函数,传递给父级 Subscription
来统一订阅。
工慾善其事,必先利其器
,想要吃透源码以前,必须深度熟悉其用法。才能知其然知其因此然。咱们先来看看高阶组件connect
用法。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?) 复制代码
mapStateToProps
const mapStateToProps = state => ({ todos: state.todos })
复制代码
做用很简单,组件依赖redux
的 state
,映射到业务组件的 props
中,state
改变触发,业务组件props
改变,触发业务组件更新视图。当这个参数没有的时候,当前组件不会订阅 store
的改变。
mapDispatchToProps
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
复制代码
将 redux
中的dispatch
方法,映射到,业务组件的props
中。
mergeProps
/* * stateProps , state 映射到 props 中的内容 * dispatchProps, dispatch 映射到 props 中的内容。 * ownProps 组件自己的 props */
(stateProps, dispatchProps, ownProps) => Object
复制代码
正常状况下,若是没有这个参数,会按照以下方式进行合并,返回的对象能够是,咱们自定义的合并规则。咱们还能够附加一些属性。
{ ...ownProps, ...stateProps, ...dispatchProps }
复制代码
options
{
context?: Object, // 自定义上下文
pure?: boolean, // 默认为 true , 当为 true 的时候 ,除了 mapStateToProps 和 props ,其余输入或者state 改变,均不会更新组件。
areStatesEqual?: Function, // 当pure true , 比较引进store 中state值 是否和以前相等。 (next: Object, prev: Object) => boolean
areOwnPropsEqual?: Function, // 当pure true , 比较 props 值, 是否和以前相等。 (next: Object, prev: Object) => boolean
areStatePropsEqual?: Function, // 当pure true , 比较 mapStateToProps 后的值 是否和以前相等。 (next: Object, prev: Object) => boolean
areMergedPropsEqual?: Function, // 当 pure 为 true 时, 比较 通过 mergeProps 合并后的值 , 是否与以前等 (next: Object, prev: Object) => boolean
forwardRef?: boolean, //当为true 时候,能够经过ref 获取被connect包裹的组件实例。
}
复制代码
options
能够是如上属性,上面已经标注了每个属性的做用,这里就很少说了。
对于connect
组件 ,咱们先看源码一探究竟
/src/connect/connect.js
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) {
return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) {
/* 通过代理包装后的 mapStateToProps */
const initMapStateToProps = match( mapStateToProps, mapStateToPropsFactories,'mapStateToProps' )
/* 通过代理包装后的 mapDispatchToProps */
const initMapDispatchToProps = match( mapDispatchToProps, mapDispatchToPropsFactories,'mapDispatchToProps')
/* 通过代理包装后的 mergeProps */
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
return connectHOC(selectorFactory, {
methodName: 'connect',
getDisplayName: name => `Connect(${name})`,
shouldHandleStateChanges: Boolean(mapStateToProps),
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
...extraOptions
})
}
}
export default /*#__PURE__*/ createConnect()
复制代码
咱们先来分析一下整个函数作的事。
1 首先定一个 createConnect
方法。 传入了几个默认参数,有两个参数很是重要,connectHOC
做为整个 connect
的高阶组件。selectorFactory
作为整合connect
更新过程当中的造成新props
的主要函数。默认的模式是pure
模式。
2 而后执行createConnect
方法,返回真正的connect
函数自己。connect
接收几个参数,而后和默认的函数进行整合,包装,代理,最后造成三个真正的初始化函数,这里的过程咱们就先不讲了。咱们接下来分别介绍这三个函数的用途。
initMapStateToProps ,用于造成真正的 MapStateToProps
函数,将 store 中 state ,映射到 props
initMapDispatchToProps,用于造成真正的 MapDispatchToProps
,将 dispatch
和 自定义的 dispatch
注入到props
。
initMergeProps,用于造成真正的 mergeProps
函数,合并业务组件的 props
, state
映射的 props
, dispatch
映射的 props
。
这里有一个函数很是重要,这个函数就是mergeProps
, 请你们记住这个函数,由于这个函数是判断整个connect
是否更新组件关键所在。上边说过 connect
基本用法的时候说过,当咱们不向connect
传递第三个参数mergeProps
的时候,默认的defaultMergeProps
以下
/src/connect/mergeProps.js
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
复制代码
这个函数返回了一个新的对象,也就是新的props
。并且将 业务组件 props
, store
中的 state
,和 dispatch
结合到一块儿,造成一个新对象,做为新的 props
传递给了业务组件。
前面说到selectorFactory
很重要,用于造成新的props
,咱们记下来看selectorFactory
源码。
/src/connect/selectorFactory.js
export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options } ) {
// mapStateToProps mapDispatchToProps mergeProps 为真正connect 通过一层代理的 proxy 函数
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
const selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory
// 返回一个 函数用于生成新的 props
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
复制代码
finalPropsSelectorFactory
的代码很简单, 首先获得真正connect 通过一层代理函数 mapStateToProps
,mapDispatchToProps
,mergeProps
。而后调用selectorFactory
(在pure
模式下,selectorFactory
就是 pureFinalPropsSelectorFactory
) 。
能够这里反复用了闭包,能够刚开始有点蒙,不过静下心来看发现其实不是很难。因为默认是pure
,因此咱们接下来主要看 pureFinalPropsSelectorFactory 函数作了些什么。
/** pure组件处理 , 对比 props 是否发生变化 而后 合并props */
export function pureFinalPropsSelectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, { areStatesEqual, areOwnPropsEqual, areStatePropsEqual } //判断 state prop 是否相等 ) {
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
/* 第一次 直接造成 ownProps stateProps dispatchProps 合并 造成新的 props */
function handleFirstCall(firstState, firstOwnProps) {
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
function handleNewPropsAndNewState() {
// props 和 state 都改变 mergeProps
}
function handleNewProps() {
// props 改变 mergeProps
}
function handleNewState() {
// state 改变 mergeProps
}
/* 不是第一次的状况 props 或者 store.state 发生改变的状况。 */
function handleSubsequentCalls(nextState, nextOwnProps) {
/* 判断两次 props 是否相等 */
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
/* 判断两次 store.state 是否相等 */
const stateChanged = !areStatesEqual(nextState, state)
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
复制代码
这个函数处理逻辑很清晰。大体上作了这些事。经过闭包的形式返回一个函数pureFinalPropsSelector
。pureFinalPropsSelector
经过判断是不是第一次初始化组件。
若是是第一次,那么直接调用mergeProps
合并ownProps
,stateProps
,dispatchProps
造成最终的props
。 若是不是第一次,那么判断究竟是props
仍是 store.state
发生改变,而后针对那里变化,从新生成对应的props
,最终合并到真正的props
。
整个 selectorFactory
逻辑就是造成新的props
传递给咱们的业务组件。
接下来咱们看一下 connect
返回的 connectAdvanced()
到底作了什么,为了方便你们理解connect
,咱们这里先看看 connect
用法。
正常模式下:
const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
function Index(){
/* ..... */
return <div> { /* .... */ } </div>
}
export default connect(mapStateToProp)(Index)
复制代码
装饰器模式下:
const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
@connect(mapStateToProp)
class Index extends React.Component{
/* .... */
render(){
return <div> { /* .... */ } </div>
}
}
复制代码
咱们上面讲到,connect
执行 接受 mapStateToProp
等参数,最后返回 connectAdvanced()
,那么上述例子中connect
执行第一步connect(mapStateToProp)===connectAdvanced()
,也就是connectAdvanced()
执行返回真正的hoc
,用于包裹咱们的业务组件。
接下来咱们看 connectAdvanced
代码
/src/components/connectAdvanced.js
export default function connectAdvanced( selectorFactory, // 每次 props,state改变执行 ,用于生成新的 props。 { getDisplayName = name => `ConnectAdvanced(${name})`, //可能被包装函数(如connect())重写 methodName = 'connectAdvanced', //若是定义了,则传递给包装元素的属性的名称,指示要呈现的调用。用于监视react devtools中没必要要的从新渲染。 renderCountProp = undefined, shouldHandleStateChanges = true, //肯定此HOC是否订阅存储更改 storeKey = 'store', withRef = false, forwardRef = false, // 是否 用 forwarRef 模式 context = ReactReduxContext,// Provider 保存的上下文 ...connectOptions } = {} ) {
/* ReactReduxContext 就是store存在的context */
const Context = context
/* WrappedComponent 为connect 包裹的组件自己 */
return function wrapWithConnect(WrappedComponent){
// WrappedComponent 被 connect 的业务组件自己
}
}
复制代码
connectAdvanced
接受配置参数 , 而后返回真正的 HOC
wrapWithConnect
。
// 咱们能够讲下面的表达式分解
connect(mapStateToProp)(Index)
// 执行 connect
connect(mapStateToProp)
//返回
connectAdvanced()
//返回HOC
wrapWithConnect
复制代码
接下来咱们分析一下wrapWithConnect
到底作了些什么?
接下来咱们来一块儿研究一下 wrapWithConnect
,咱们重点看一下 wrapWithConnect
做为高阶组件,会返回一个组件,这个组件会对原有的业务组件,进行一系列加强等工做。
function wrapWithConnect(WrappedComponent) {
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
const { pure } = connectOptions
function createChildSelector(store) {
// 合并函数 mergeprops 获得最新的props
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
//判断是不是pure纯组件模式 若是是 将用 useMemo 提高性能
const usePureOnlyMemo = pure ? useMemo : callback => callback()
// 负责更新的容器子组件
function ConnectFunction (props){
// props 为 业务组件 真正的 props
}
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
/* forwardRef */
if (forwardRef) {
const forwarded = React.forwardRef(function forwardConnectRef( props, ref ) {
return <Connect {...props} reactReduxForwardedRef={ref} />
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)
}
}
复制代码
wrapWithConnect
的作的事大体分为一下几点:
1 声明负责更新的 ConnectFunction
无状态组件。和负责合并 props
的createChildSelector
方法
2 判断是不是 pure
纯组件模式,若是是用react.memo
包裹,这样作的好处是,会向 pureComponent
同样对 props
进行浅比较。
3 若是 connect
有forwardRef
配置项,用React.forwardRef
处理,这样作好处以下。
正常状况下由于咱们的WrappedComponent
被 connect
包装,因此不能经过ref
访问到业务组件WrappedComponent
的实例。
子组件
const mapStateToProp = (store) => ({ userInfo: store.root.userInfo })
class Child extends React.Component{
render(){
/* ... */
}
}
export default connect(mapStateToProp)(Child)
复制代码
父组件
class Father extends React.Compoent{
child = null
render(){
return <Child ref={(cur)=> this.child = cur } { /* 获取到的不是`Child`自己 */ } />
}
}
复制代码
咱们没法经过 ref
访问到 Child
组件。
因此咱们能够经过 options
的 forwardRef
属性设置为 true
,这样就能够根本解决问题。
connect(mapStateToProp,mapDispatchToProps,mergeProps,{ forwardRef:true })(Child)
复制代码
hoistStatics(Connect, WrappedComponent)
复制代码
最后作的事情就是经过hoistStatics
库 把子组件WrappedComponent
的静态方法/属性,继承到父组件Connect
上。由于在 高阶组件 包装 业务组件的过程当中,若是不对静态属性或是方法加以额外处理,是不会被包装后的组件访问到的,因此须要相似hoistStatics
这样的库,来作处理。
接下来说的就是整个 connect
的核心了。咱们来看一下负责更新的容器ConnectFunction
到底作了些什么?
ConnectFunction
的代码很复杂,须要咱们一步步去吃透,一步步去消化。
function ConnectFunction(props) {
/* TODO: 第一步 把 context ForwardedRef props 取出来 */
const [
reactReduxForwardedRef,
wrapperProps // props 传递的props
] = useMemo(() => {
const { reactReduxForwardedRef, ...wrapperProps } = props
return [reactReduxForwardedRef, wrapperProps]
}, [props])
// 获取 context内容 里面含有 redux 中store 和 subscription
const contextValue = useContext(Context)
//TODO: 判断 store 是否来此 props didStoreComeFromProps ,正常状况下 ,prop 中是不存在 store 因此 didStoreComeFromProps = false
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
// 获取 redux 中 store
const store = didStoreComeFromProps ? props.store : contextValue.store
// 返回merge函数 用于生成真正传给子组件 props
const childPropsSelector = useMemo(() => {
return createChildSelector(store)
}, [store])
// TODO: 第二步 subscription 监听者实例
const [subscription, notifyNestedSubs] = useMemo(() => {
// 若是没有订阅更新,那么直接返回。
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription // 和 上级 `subscription` 创建起关系。 this.parentSub = contextValue.subscription
)
// notifyNestedSubs 触发 noticy 全部子代 listener 监听者 -> 触发batch方法,触发 batchupdate方法 ,批量更新
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
/* 建立出一个新的contextValue ,把父级的 subscription 换成本身的 subscription */
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
return contextValue
}
return {
...contextValue,
subscription
}
}, [didStoreComeFromProps, contextValue, subscription])
const [
[previousStateUpdateResult],
forceComponentUpdateDispatch /* */
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
// TODO: 第三步
const lastChildProps = useRef() //保存上一次 合并过的 props信息(通过 ownprops ,stateProps , dispatchProps 合并过的 )
const lastWrapperProps = useRef(wrapperProps) // 保存本次上下文执行 业务组件的 props
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false) // 当前组件是否处于渲染阶段
// actualChildProps 为当前真正处理事后,通过合并的 props
const actualChildProps = usePureOnlyMemo(() => {
// 调用 mergeProps 进行合并,返回合并后的最新 porps
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
/* 负责更新缓存变量,方便下一次更新的时候比较 */
useEffect(()=>{
captureWrapperProps(...[
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs
])
})
useEffect(()=>{
subscribeUpdates(...[
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch
])
},[store, subscription, childPropsSelector])
// TODO: 第四步:reactReduxForwardedRef 是处理父级元素是否含有 forwardRef 的状况 这里能够忽略。
const renderedWrappedComponent = useMemo(
() => (
<WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} />
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
const renderedChild = useMemo(() => {
//shouldHandleStateChanges 来源 connect是否有第一个参数
if (shouldHandleStateChanges) {
return (
// ContextToUse 传递 context
<ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider>
)
}
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
}
复制代码
为了方便你们更直观的理解,我这里保留了影响流程的核心代码,我会一步步分析 整个核心部分。想要弄明白这里,须要对 react-hooks
和 provider
有一些了解。
经过 props
分离出 reactReduxForwardedRef
, wrapperProps
。reactReduxForwardedRef
是当开启 ForwardedRef
模式下,父级传过来的 React.forwaedRef
。
而后判断经过常量didStoreComeFromProps
储存当前,redux.store
是否来自 props
, 正常状况下,咱们的 store
都来自 provider
,不会来自props
,因此咱们能够把didStoreComeFromProps = true
。接下来咱们获取到 store
,经过 store
来判断是否更新真正的合并props
函数childPropsSelector
。
subscription
, 层层传递新的 context
(很重要)这一步很是重要,判断经过shouldHandleStateChanges
判断此 HOC
是否订阅存储更改,若是已经订阅了更新(此时connect
具备第一个参数),那么建立一个 subscription
,而且和上一层provider
的subscription
创建起关联。this.parentSub = contextValue.subscription
。而后分离出 subscription
和 notifyNestedSubs
(notifyNestedSubs
的做用是通知当前subscription
的 listeners
进行更新的方法。 ) 。
而后经过 useMemo
建立出一个新的 contextValue
,把父级的 subscription
换成本身的 subscription
。用于经过 Provider
传递新的 context
。这里简单介绍一下,运用了 Provider
能够和多个消费组件有对应关系。多个 Provider
也能够嵌套使用,里层的会覆盖外层的数据。react-redux
用context
更倾向于Provider
良好的传递上下文的能力。
接下来经过useReducer
制造出真正触发更新的forceComponentUpdateDispatch
函数。也就是整个 state
或者是 props
改变,触发组件更新的函数。 为何这么作呢?
笔者认为react-redxx
这样设计缘由是但愿connect
本身控制本身的更新,而且多个上下级 connect
不收到影响。因此一方面经过useMemo
来限制业务组件没必要要的更新,另外一方面来经过forceComponentUpdateDispatch
来更新 HOC
函数,产生actualChildProps
,actualChildProps
改变 ,useMemo
执行,触发组件渲染。
这一步十分重要,为何这么说呢,首先先经过useRef
缓存几个变量:
lastChildProps -> 保存上一次 合并过的 props
信息(通过 ownprops
,stateProps
, dispatchProps
合并过的 )。 lastWrapperProps -> 保存本次上下文执行 业务组件的 props
。 renderIsScheduled -> 当前组件是否处于渲染阶段。 actualChildProps -> actualChildProps
为当前真正处理事后,通过合并的 props
, 组件经过 dep
-> actualChildProps
,来判断是否进行更新。
接下来执行两次 useEffect
, 源码中不是这个样子的,我这里通过简化,第一个 useEffect
执行了 captureWrapperProps
,captureWrapperProps
是干什么的呢?
//获取包装的props
function captureWrapperProps( lastWrapperProps, lastChildProps, renderIsScheduled, wrapperProps, actualChildProps, childPropsFromStoreUpdate, notifyNestedSubs ) {
lastWrapperProps.current = wrapperProps //子props
lastChildProps.current = actualChildProps //通过 megeprops 以后造成的 prop
renderIsScheduled.current = false // 当前组件渲染完成
}
复制代码
captureWrapperProps
的做用很简单,在一次组件渲染更新后,将上一次 合并前 和 合并后 的props
,保存起来。这么作目的是,能过在两次hoc
执行渲染中,对比props stateProps
是否发生变化。从而肯定是否更新 hoc
,进一步更新组件。
执行第二个 useEffect
是很关键。执行subscribeUpdates
函数,subscribeUpdates
是订阅更新的主要函数,咱们一块儿来看看:
function subscribeUpdates( shouldHandleStateChanges, store, subscription, childPropsSelector, lastWrapperProps, //子props lastChildProps, //通过 megeprops 以后造成的 prop renderIsScheduled, childPropsFromStoreUpdate, notifyNestedSubs, forceComponentUpdateDispatch ) {
if (!shouldHandleStateChanges) return
// 捕获值以检查此组件是否卸载以及什么时候卸载
let didUnsubscribe = false
let lastThrownError = null
//store更新订阅传播到此组件时,运行此回调
const checkForUpdates = ()=>{
//....
}
subscription.onStateChange = checkForUpdates
//开启订阅者 ,当前是被connect 包转的状况 会把 当前的 checkForceUpdate 放在存入 父元素的addNestedSub中。
subscription.trySubscribe()
//在第一次呈现以后从存储中提取数据,以防存储从咱们开始就改变了。
checkForUpdates()
/* 卸载订阅起 */
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
return unsubscribeWrapper
}
复制代码
这绝对是整个订阅更新的核心,首先声明 store
更新订阅传播到此组件时的回调函数checkForUpdates
把它赋值给onStateChange
,若是store
中的state
发生改变,那么在组件订阅了state
内容以后,相关联的state
改变就会触发当前组件的onStateChange
,来合并获得新的props
,从而触发组件更新。
而后subscription.trySubscribe()
把订阅函数onStateChange
绑定给父级subscription
,进行了层层订阅。
最后,为了防止渲染后,store
内容已经改变,因此首先执行了一次checkForUpdates
。那么checkForUpdates
的做用很明确了,就是检查是否派发当前组件的更新。
到这里咱们明白了,react-redux
经过 subscription
进行层层订阅。对于一层层的组件结构,总体模型图以下:。
接下来咱们看一下checkForUpdates
//store更新订阅传播到此组件时,运行此回调
const checkForUpdates = () => {
if (didUnsubscribe) {
//若是写在了
return
}
// 获取 store 里state
const latestStoreState = store.getState()q
let newChildProps, error
try {
/* 获得最新的 props */
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
}
//若是新的合并的 props没有更改,则此处不作任何操做-层叠订阅更新
if (newChildProps === lastChildProps.current) {
if (!renderIsScheduled.current) {
notifyNestedSubs() /* 通知子代 subscription 触发 checkForUpdates 来检查是否须要更新。 */
}
} else {
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
// 此状况 可能考虑到 代码运行到这里 又发生了 props 更新 因此触发一个 reducer 来促使组件更新。
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error
}
})
}
}
复制代码
checkForUpdates
经过调用 childPropsSelector
来造成新的props
,而后判断以前的 prop
和当前新的 prop
是否相等。若是相等,证实没有发生变化,无须更新当前组件,那么经过调用notifyNestedSubs
来通知子代容器组件,检查是否须要更新。若是不相等证实订阅的store.state
发生变化,那么当即执行forceComponentUpdateDispatch
来触发组件的更新。
对于层层订阅的结构,整个更新模型图以下:
接下来咱们总结一下整个connect
的流程。咱们仍是从订阅和更新两个方向入手。
整个订阅的流程是,若是被connect
包裹,而且具备第一个参数。首先经过context
获取最近的 subscription
,而后建立一个新的subscription
,而且和父级的subscription
创建起关联。当第一次hoc
容器组件挂在完成后,在useEffect
里,进行订阅,将本身的订阅函数checkForUpdates
,做为回调函数,经过trySubscribe
和this.parentSub.addNestedSub
,加入到父级subscription
的listeners
中。由此完成整个订阅流程。
整个更新流程是,那state
改变,会触发根订阅器的store.subscribe
,而后会触发listeners.notify
,也就是checkForUpdates
函数,而后checkForUpdates
函数首先根据mapStoretoprops
,mergeprops
等操做,验证该组件是否发起订阅,props
是否改变,并更新,若是发生改变,那么触发useReducer
的forceComponentUpdateDispatch
函数,来更新业务组件,若是没有发生更新,那么经过调用notifyNestedSubs
,来通知当前subscription
的listeners
检查是否更新,而后尽心层层checkForUpdates
,逐级向下,借此完成整个更新流程。
useMemo
用法思考?整个react-redux
源码中,对于useMemo
用法仍是蛮多的,我总结了几条,奉上🌹🌹:
react-redux
源码中,多处应用了useMemo
依赖/缓存 属性的状况。这样作的好处是只有依赖项发生改变的时候,才更新新的缓存属性/方法,好比 childPropsSelector
, subscription
, actualChildProps
等主要方法属性。
react-redux
源码中,经过 useMemo
来控制业务组件是否渲染。经过 actualChildProps
变化,来证实是否来自 **自身 props
** 或 订阅的 state
的修改,来肯定是否渲染组件。
例子🌰:
const renderedWrappedComponent = useMemo(
() => (
<WrappedComponent {...actualChildProps} ref={reactReduxForwardedRef} />
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
复制代码
但愿这篇文章能让屏幕前的你,对react-redux
的订阅和更新流程有一个新的认识。送人玫瑰,手留余香,阅读的朋友能够给笔者点赞,关注一波 ,陆续更新前端超硬核文章。
回看笔者往期高赞文章,有更多精彩内容等着你!
「react进阶」年终送给react开发者的八条优化建议 800+
赞👍
Vue组件通讯方式及其应用场景总结 250+
赞👍
h5,小程序飞入购物车(抛物线绘制运动轨迹点) 300
赞 👍
vue3.0源码系列
vue3.0 响应式原理(超详细) 200+
赞👍
全面解析 vue3.0 diff算法 100+
赞👍
vue3.0 watch 和 computed源码解析 30+
赞👍
react-hooks系列
玩转react-hooks,自定义hooks设计模式及其实战 150+
👍赞
react-hooks如何使用 70+
赞👍
开源项目系列
230+
赞 👍