欢迎关注公众号: 一口一个前端,不按期分享我所理解的前端知识前端
以前写了一篇解读Redux运行机制的文章,以后一直想再写一篇React-Redux的解析,但这个源码比较复杂,好在最近收获了一些东西,分享出来。react
我在读React-Redux源码的过程当中,很天然的要去网上找一些参考文章,但发现这些文章基本都没有讲的很透彻,不少时候就是平铺直叙把API挨个讲一下,并且只讲某一行代码是作什么的,却没有结合应用场景和用法解释清楚为何这么作,加上源码自己又很抽象,函数间的调用关系很是很差梳理清楚,最终结果就是越看越懵。我此次将尝试换一种解读方式,由最多见的用法入手,结合用法,提出问题,带着问题看源码里是如何实现的,以此来和你们一块儿逐渐梳理清楚React-Redux的运行机制。redux
文章用了一周多的时间写完,粗看了一遍源码以后,又边看边写。源码不算少,我尽可能把结构按照最容易理解的方式梳理,努力按照浅显的方式将原理讲出来, 但架不住代码结构的复杂,不少地方依然须要花时间思考,捋清函数之间的调用关系并结合用法才能明白。文章有点长,能看到最后的都是真爱~数组
水平有限,不免有地方解释的不到位或者有错误,也但愿你们能帮忙指出来,不胜感激。浏览器
在这里,我就默认你们已经会使用Redux了,它为咱们的应用提供一个全局的对象(store)来管理状态。 那么如何将Redux应用在React中呢?想一下,咱们的最终目的是实现跨层级组件间通讯与状态的统一管理。因此可使用Context这个特性。缓存
而这些都须要本身手动去作,React-Redux将上边的都封装起来了。让咱们经过一段代码看一下React-Redux的用法:bash
首先是在React的最外层应用上,包裹Provider,而Provider是React-Redux提供的组件,这里作的事情至关于上边的第一步antd
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
const reducer = (state, actions) => {
...
}
const store = createStore(reducer)
...
class RootApp extends React.Component {
render() {
// 这里将store传入Provider
return <Provider store={store}>
<App/>
</Provider>
}
}
复制代码
第二步中的订阅,已经分别在Provider和connect中实现了闭包
再看应用内的子组件。若是须要从store中拿数据或者更新store数据的话(至关于上边的第三步和第四步), 须要用connect将组件包裹起来:app
import React from 'react'
import { connect } from '../../react-redux-src'
import { increaseAction, decreaseAction } from '../../actions/counter'
import { Button } from 'antd'
class Child extends React.Component {
render() {
const { increaseAction, decreaseAction, num } = this.props
return <div>
{num}
<Button onClick={() => increaseAction()}>增长</Button>
<Button onClick={() => decreaseAction()}>减小</Button>
</div>
}
}
const mapStateToProps = (state, ownProps) => {
const { counter } = state
return {
num: counter.num
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increaseAction: () => dispatch({
type: INCREASE
}),
decreaseAction: () => dispatch({
type: DECREASE
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Child)
复制代码
mapStateToProps 用于创建组件和store中存储的状态的映射关系,它是一个函数,第一个参数是state,也就是redux中存储的顶层数据,第二个参数是组件自身的props。返回一个对象,对象内的字段就是该组件须要从store中获取的值。
mapDispatchToProps用于创建组件和store.dispatch的映射关系。它能够是一个对象,也能够是一个函数, 当它是一个函数的时候,第一个参数就是dispatch,第二个参数是组件自身的props。
mapDispatchToProps的对象形式以下:
const mapDispatchToProps = {
increaseAction() {
return dispatch => dispatch({
type: INCREASE
})
},
decreaseAction() {
return dispatch => dispatch({
type: DECREASE
})
}
}
复制代码
当不传mapStateToProps的时候,当store变化的时候,不会引发组件UI的更新。
当不传mapDispatchToProps的时候,默认将dispatch注入到组件的props中。
以上,若是mapStateToProps 或者mapDispatchToProps传了ownProps,那么在组件自身的props变化的时候,这两个函数也都会被调用。
咱们先给出结论,说明React-Redux作了什么工做:
有了上边的结论,但想必你们都比较好奇到底是怎么实现的,上边的几项工做都是协同完成的,最终的表象体现为下面几个问题:
接下来,带着这些问题来一条一条地分析源码。
先从Provider组件入手,代码很少,直接上源码
class Provider extends Component {
constructor(props) {
super(props)
// 从props中取出store
const { store } = props
this.notifySubscribers = this.notifySubscribers.bind(this)
// 声明一个Subscription实例。订阅,监听state变化来执行listener,都由实例来实现。
const subscription = new Subscription(store)
// 绑定监听,当state变化时,通知订阅者更新页面,实际上也就是在connect过程当中被订阅到react-redux的subscrption对象上的更新函数
subscription.onStateChange = this.notifySubscribers
// 将store和subscription放入state中,稍后this.state将会做为context的value
this.state = {
store,
subscription
}
// 获取当前的store中的state,做为上一次的state,将会在组件挂载完毕后,
// 与store新的state比较,不一致的话更新Provider组件
this.previousState = store.getState()
}
componentDidMount() {
this._isMounted = true
// 在组件挂载完毕后,订阅更新。至于如何订阅的,在下边讲到Subscription类的时候会讲到,
// 这里先理解为最开始的时候须要订阅更新函数,便于在状态变化的时候执行更新函数
this.state.subscription.trySubscribe()
// 若是先后的store中的state有变化,那么就去更新Provider组件
if (this.previousState !== this.props.store.getState()) {
this.state.subscription.notifyNestedSubs()
}
}
componentWillUnmount() {
// 组件卸载的时候,取消订阅
if (this.unsubscribe) this.unsubscribe()
this.state.subscription.tryUnsubscribe()
this._isMounted = false
}
componentDidUpdate(prevProps) {
// 在组件更新的时候,检查一下当前的store与以前的store是否一致,若不一致,说明应该根据新的数据作变化,
// 那么依照原来的数据作出改变已经没有意义了,因此会先取消订阅,再从新声明Subscription实例,
// 绑定监听,设置state为新的数据
if (this.props.store !== prevProps.store) {
this.state.subscription.tryUnsubscribe()
const subscription = new Subscription(this.props.store)
subscription.onStateChange = this.notifySubscribers
this.setState({ store: this.props.store, subscription })
}
}
notifySubscribers() {
// notifyNestedSubs() 实际上会通知让listener去执行,做用也就是更新UI
this.state.subscription.notifyNestedSubs()
}
render() {
const Context = this.props.context || ReactReduxContext
// 将this.state做为context的value传递下去
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.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实例。保证用的数据都是最新的。
我猜测应该有一个缘由是考虑到了Provider有可能被嵌套使用,因此会有这种在Provider更新以后取新数据并从新订阅的作法,这样才能保证每次传给子组件的context是最新的。
Provider将执行触发listeners执行的函数订阅到了store。
咱们已经发现了,Provider组件是经过Subscription类中的方法来实现更新的,而过一会要讲到的connect高阶组件的更新,也是经过它来实现,可见Subscription是React-Redux实现订阅更新的核心机制。
import { getBatch } from './batch'
const CLEARED = null
const nullListeners = { notify() {} }
function createListenerCollection() {
const batch = getBatch()
let current = []
let next = []
return {
clear() {
// 清空next和current
next = CLEARED
current = CLEARED
},
notify() {
// 将next赋值给current,并同时赋值给listeners,这里的next、current、listeners其实就是订阅的更新函数队列
const listeners = (current = next)
// 批量执行listeners
batch(() => {
for (let i = 0; i < listeners.length; i++) {
// 执行更新函数,这是触发UI更新的最根本的原理
listeners[i]()
}
})
},
get() {
return next
},
subscribe(listener) {
let isSubscribed = true
// 将current复制一份,并赋值给next,下边再向next中push listener(更新页面的函数)
if (next === current) next = current.slice()
next.push(listener)
return function unsubscribe() {
if (!isSubscribed || current === CLEARED) return
isSubscribed = false
// 最终返回一个取消订阅的函数,用于在下一轮的时候清除没用的listener
if (next === current) next = current.slice()
next.splice(next.indexOf(listener), 1)
}
}
}
}
export default class Subscription {
constructor(store, parentSub) {
// 获取store,要经过store来实现订阅
this.store = store
// 获取来自父级的subscription实例,主要是在connect的时候可能会用到
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
addNestedSub(listener) {
this.trySubscribe()
// 由于这里是被parentSub调用的,因此listener也会被订阅到parentSub上,也就是从Provider中获取的subscription
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
// 通知listeners去执行
this.listeners.notify()
}
handleChangeWrapper() {
if (this.onStateChange) {
// onStateChange会在外部的被实例化成subcription实例的时候,被赋值为不一样的更新函数,被赋值的地方分别的Provider和connect中
// 因为刚刚被订阅的函数就是handleChangeWrapper,而它也就至关于listener。因此当状态变化的时候,listener执行,onStateChange会执行
this.onStateChange()
}
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
// parentSub其实是subcription实例
// 这里判断的是this.unsubscribe被赋值后的值,本质上也就是判断parentSub有没有,顺便再赋值给this.unsubscribe
// 若是parentSub没传,那么使用store订阅,不然,调用context中获取的subscrption来订阅,保证都订阅到一个地方,具体会在下边说明
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
: this.store.subscribe(this.handleChangeWrapper)
// 建立listener集合
this.listeners = createListenerCollection()
}
}
tryUnsubscribe() {
// 取消订阅
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
复制代码
Subscription就是将页面的更新工做和状态的变化联系起来,具体就是listener(触发页面更新的方法,在这里就是handleChangeWrapper),经过trySubscribe方法,根据状况被分别订阅到store或者Subscription内部。放入到listeners数组,当state变化的时候,listeners循环执行每个监听器,触发页面更新。
说一下trySubscribe中根据不一样状况判断直接使用store订阅,仍是调用addNestedSub来实现订阅的缘由。由于前者的场景是Provider将listener订阅到store中,此时的listeners数组内实际上是每一个connect内部的checkForUpdates函数(后边会讲到)。后者是connect内部将checkForUpdates放到listeners数组中,其实是利用Provider中传过来的Subscrption实例来订阅,保证全部被connect的组件都订阅到一个Subscrption实例上。
将store从应用顶层注入后,该考虑如何向组件中注入state和dispatch了。
正常顺序确定是先拿到store,再以某种方式分别执行这两个函数,将store中的state和dispatch,以及组件自身的props做为mapStateToProps和mapDispatchToProps的参数,传进去,咱们就能够在这两个函数以内能拿到这些值。而它们的返回值,又会再注入到组件的props中。
说到这里,就要引出一个概念:selector。最终注入到组件的props是selectorFactory函数生成的selector的返回值,因此也就是说,mapStateToProps和mapDispatchToProps本质上就是selector。
生成的过程是在connect的核心函数connectAdvanced中,这个时候能够拿到当前context中的store,进而用store传入selectorFactory生成selector,其形式为
function selector(stateOrDispatch, ownProps) {
...
return props
}
复制代码
经过形式能够看出:selector就至关于mapStateToProps或者mapDispatchToProps,selector的返回值将做为props注入到组件中。
标题的mapToProps泛指mapStateToProps, mapDispatchToProps, mergeProps
结合平常的使用可知,咱们的组件在被connect包裹以后才能拿到state和dispatch,因此咱们先带着上边的结论,单独梳理selector的机制,先看connect的源码:
export function createConnect({
connectHOC = connectAdvanced, // connectAdvanced函数是connect的核心
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{...options} = {}
) {
// 将咱们传入的mapStateToProps, mapDispatchToProps, mergeProps都初始化一遍
const initMapStateToProps = match(mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
// 返回connectHOC函数的调用,connectHOC的内部是connect的核心
return connectHOC(selectorFactory, {
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
...
})
}
}
export default createConnect()
复制代码
connect其实是createConnect,createConnect也只是返回了一个connect函数,而connect函数返回了connectHOC的调用(也就是connectAdvanced的调用),再继续,connectAdvanced的调用最终会返回一个wrapWithConnect高阶组件,这个函数的参数是咱们传入的组件。因此才有了connect日常的用法:
connect(mapStateToProps, mapDispatchToProps)(Component)
复制代码
你们应该注意到了connect函数内将mapStateToProps,mapDispatchToProps,mergeProps都初始化了一遍,为何要去初始化而不直接使用呢?带着疑问,咱们往下看。
先看代码,主要看initMapStateToProps 和 initMapDispatchToProps,看一下这段代码是什么意思。
const initMapStateToProps = match(mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps')
const initMapDispatchToProps = match(mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps')
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
复制代码
mapStateToPropsFactories 和 mapDispatchToPropsFactories都是函数数组,其中的每一个函数都会接收一个参数,为mapStateToProps或者mapDispatchToProps。而match函数的做用就是循环函数数组,mapStateToProps或者mapDispatchToProps做为每一个函数的入参去执行,当此时的函数返回值不为假的时候,赋值给左侧。看一下match函数:
function match(arg, factories, name) {
// 循环执行factories,这里的factories也就是mapStateToProps和mapDisPatchToProps两个文件中暴露出来的处理函数数组
for (let i = factories.length - 1; i >= 0; i--) {
// arg也就是mapStateToProps或者mapDispatchToProps
// 这里至关于将数组内的每一个函数之星了一遍,并将咱们的mapToProps函数做为参数传进去
const result = factories[i](arg)
if (result) return result
}
}
复制代码
match循环的是一个函数数组,下面咱们看一下这两个数组,分别是mapStateToPropsFactories 和 mapDispatchToPropsFactories: (下边源码中的whenMapStateToPropsIsFunction函数会放到后边讲解)
mapStateToPropsFactories
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
// 当mapStateToProps是函数的时候,调用wrapMapToPropsFunc
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
// 当mapStateToProps没有传的时候,调用wrapMapToPropsConstant
export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
复制代码
其实是让whenMapStateToPropsIsFunction
和whenMapStateToPropsIsMissing
都去执行一次mapStateToProps,而后根据传入的mapStateToProps的状况来选出有执行结果的函数赋值给initMapStateToProps。
单独看一下whenMapStateToPropsIsMissing
export function wrapMapToPropsConstant(getConstant) {
return function initConstantSelector(dispatch, options) {
const constant = getConstant(dispatch, options)
function constantSelector() {
return constant
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}
复制代码
wrapMapToPropsConstant返回了一个函数,接收的参数是咱们传入的() => ({}),函数内部调用了入参函数并赋值给一个常量放入了constantSelector中, 该常量实际上就是咱们不传mapStateToProps时候的生成的selector,这个selector返回的是空对象,因此不会接受任何来自store中的state。同时能够看到constantSelector.dependsOnOwnProps = false,表示返回值与connect高阶组件接收到的props无关。
mapDispatchToPropsFactories
import { bindActionCreators } from '../../redux-src'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === 'function'
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
// 当不传mapDispatchToProps时,默认向组件中注入dispatch
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant(dispatch => ({ dispatch }))
: undefined
}
// 当传入的mapDispatchToProps是对象,利用bindActionCreators进行处理 详见redux/bindActionCreators.js
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant(dispatch => bindActionCreators(mapDispatchToProps, dispatch))
: undefined
}
export default [
whenMapDispatchToPropsIsFunction,
whenMapDispatchToPropsIsMissing,
whenMapDispatchToPropsIsObject
]
复制代码
没有传递mapDispatchToProps的时候,会调用whenMapDispatchToPropsIsMissing,这个时候,constantSelector只会返回一个dispatch,因此只能在组件中接收到dispatch。
当传入的mapDispatchToProps是对象的时候,也是调用wrapMapToPropsConstant,根据前边的了解,这里注入到组件中的属性是 bindActionCreators(mapDispatchToProps, dispatch)的执行结果。
如今,让咱们看一下whenMapStateToPropsIsFunction这个函数。它是在mapDispatchToProps与mapStateToProps都是函数的时候调用的,实现也比较复杂。这里只单用mapStateToProps来举例说明。
再提醒一下:下边的mapToProps指的是mapDispatchToProps或mapStateToProps
// 根据mapStateToProps函数的参数个数,判断组件是否应该依赖于本身的props
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
export function wrapMapToPropsFunc(mapToProps, methodName) {
// 最终wrapMapToPropsFunc返回的是一个proxy函数,返回的函数会在selectorFactory函数中
// 的finalPropsSelectorFactory内被调用并赋值给其余变量。
// 而这个proxy函数会在selectorFactory中执行,生成最终的selector
return function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
// 根据组件是否依赖自身的props决定调用的时候传什么参数
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
// 将proxy.mapToProps赋值为咱们传入的mapToProps
proxy.mapToProps = mapToProps
// 根据组件是否传入了组件自己从父组件接收的props来肯定是否须要向组件中注入ownProps,
// 最终会用来实现组件自身的props变化,也会调用mapToProps的效果
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
// 再去执行proxy,这时候proxy.mapToProps已经被赋值为咱们传进来的mapToProps函数,
// 因此props就会被赋值成传进来的mapToProps的返回值
let props = proxy(stateOrDispatch, ownProps)
if (typeof props === 'function') {
// 若是返回值是函数,那么再去执行这个函数,再将store中的state或dispatch,以及ownProps再传进去
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, methodName)
return props
}
return proxy
}
}
复制代码
wrapMapToPropsFunc返回的其实是initProxySelector函数,initProxySelector的执行结果是一个代理proxy,可理解为将传进来的数据(state或dispatch, ownProps)代理到咱们传进来的mapToProps函数。proxy的执行结果是proxy.mapToProps,本质就是selector。
页面初始化执行的时候,dependsOnOwnProps为true,因此执行proxy.mapToProps(stateOrDispatch, ownProps),也就是detectFactoryAndVerify。在后续的执行过程当中,会先将proxy的mapToProps赋值为咱们传入connect的mapStateToProps或者mapDispatchToProps,而后在依照实际状况组件是否应该依赖本身的props赋值给dependsOnOwnProps。(注意,这个变量会在selectorFactory函数中做为组件是否根据本身的props变化执行mapToProps函数的依据)。
总结一下,这个函数最本质上作的事情就是将咱们传入connect的mapToProps函数挂到proxy.mapToProps上,同时再往proxy上挂载一个dependsOnOwnProps来方便区分组件是否依赖本身的props。最后,proxy又被做为initProxySelector的返回值,因此初始化过程被赋值的initMapStateToProps、initMapDispatchToProps、initMergeProps其实是initProxySelector的函数引用,它们执行以后是proxy,至于它们三个proxy是在哪执行来生成具体的selector的咱们下边会讲到。
如今,回想一下咱们的疑问,为何要去初始化那三个mapToProps函数?目的很明显,就是准备出生成selector的函数,用来放到一个合适的时机来执行,同时决定selector要不要对ownProps的改变作反应。
准备好了生成selector的函数以后,就须要执行它,将它的返回值做为props注入到组件中了。先粗略的归纳一下注入的过程:
下面咱们须要从最后一步的注入开始倒推,来看selector是怎么执行的。
注入的过程发生在connect的核心函数connectAdvanced以内,先忽略该函数内的其余过程,聚焦注入过程,简单看下源码
export default function connectAdvanced(
selectorFactory,
{
getDisplayName = name => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
forwardRef = false,
context = ReactReduxContext,
...connectOptions
} = {}
) {
const Context = context
return function wrapWithConnect(WrappedComponent) {
// ...忽略了其余代码
// selectorFactoryOptions是包含了咱们初始化的mapToProps的一系列参数
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
// pure表示只有当state或者ownProps变更的时候,从新计算生成selector。
const { pure } = connectOptions
/* createChildSelector 的调用形式:createChildSelector(store)(state, ownProps),
createChildSelector返回了selectorFactory的调用,而selectorFactory其实是其内部根据options.pure返回的
impureFinalPropsSelectorFactory 或者是 pureFinalPropsSelectorFactory的调用,而这两个函数须要的参数是
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
除了dispatch,其他参数均可从selectorFactoryOptions中得到。调用的返回值,就是selector。而selector须要的参数是
(state, ownprops)。因此得出结论,createChildSelector(store)就是selector
*/
function createChildSelector(store) {
// 这里是selectorFactory.js中finalPropsSelectorFactory的调用(本质上也就是上面咱们初始化的mapToProps的调用),传入dispatch,和options
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
function ConnectFunction(props) {
const store = props.store || contextValue.store
// 仅当store变化的时候,建立selector
// 调用childPropsSelector => childPropsSelector(dispatch, options)
const childPropsSelector = useMemo(() => {
// 每当store变化的时候从新建立这个选择器
return createChildSelector(store)
}, [store])
// actualChildProps就是最终要注入到组件中的props,也就是selector的返回值。
const actualChildProps = usePureOnlyMemo(() => {
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
const renderedWrappedComponent = useMemo(
// 这里是将props注入到组件的地方
() => <WrappedComponent {...actualChildProps} />,
[forwardedRef, WrappedComponent, actualChildProps]
)
}
// 最后return出去
return hoistStatics(Connect, WrappedComponent)
}
复制代码
在注入过程当中,有一个很重要的东西:selectorFactory
。这个函数就是生成selector的很重要的一环。它起到一个上传下达的做用,把接收到的dispatch,以及那三个mapToProps函数,传入到selectorFactory内部的处理函数(pureFinalPropsSelectorFactory 或 impureFinalPropsSelectorFactory)中,selectorFactory的执行结果是内部处理函数的调用。而内部处理函数的执行结果就是将那三种selector(mapStateToProps,mapDispatchToProps,mergeProps) 执行后合并的结果。也就是最终要传给组件的props
下面咱们看一下selectorFactory的内部实现。为了清晰,只先一下内部的结构
// 直接将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并做为返回值return出去
export function impureFinalPropsSelectorFactory(){}
export function pureFinalPropsSelectorFactory() {
// 整个过程首次初始化的时候调用
function handleFirstCall(firstState, firstOwnProps) {}
// 返回新的props
function handleNewPropsAndNewState() {
// 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并做为返回值return出去
}
// 返回新的props
function handleNewProps() {
// 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并做为返回值return出去
}
// 返回新的props
function handleNewState() {
// 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并做为返回值return出去
}
// 后续的过程调用
function handleSubsequentCalls(nextState, nextOwnProps) {}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
// 第一次渲染,调用handleFirstCall,以后的action派发行为会触发handleSubsequentCalls
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
// finalPropsSelectorFactory函数是在connectAdvaced函数内调用的selectorFactory函数
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
const mapStateToProps = initMapStateToProps(dispatch, options)
// 这里是wrapMapToProps.js中wrapMapToPropsFunc函数的柯里化调用,是改造
// 以后的mapStateToProps, 在下边返回的函数内还会再调用一次
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
// 根据是否传入pure属性,决定调用哪一个生成selector的函数来计算传给组件的props。并将匹配到的函数赋值给selectorFactory
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory // 当props或state变化的时候,才去从新计算props
: impureFinalPropsSelectorFactory // 直接从新计算props
// 返回selectorFactory的调用
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
复制代码
能够看出来,selectorFactory内部会决定在何时生成新的props。下面来看一下完整的源码
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
// 若是调用这个函数,直接将三个selector的执行结果合并返回
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
// 使用闭包保存一个变量,标记是不是第一次执行
let hasRunAtLeastOnce = false
// 下边这些变量用于缓存计算结果
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
function handleFirstCall(firstState, firstOwnProps) {
state = firstState
ownProps = firstOwnProps
// 这里是wrapMapToProps.js中wrapMapToPropsFunc函数的柯里化调用的函数内部的proxy函数的调用。
stateProps = mapStateToProps(state, ownProps)
/*
* 膝盖已烂,太绕了
* 回顾一下proxy:
* const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {}
* return proxy
* */
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
// 返回计算后的props
return mergedProps
}
function handleNewPropsAndNewState() {
stateProps = mapStateToProps(state, ownProps)
// 因为这个函数的调用条件是ownProps和state都变化,因此有必要判断一下dependsOnOwnProps
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewProps() {
// 判断若是须要依赖组件本身的props,从新计算stateProps
if (mapStateToProps.dependsOnOwnProps) {
stateProps = mapStateToProps(state, ownProps)
}
// 同上
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
// 将组件本身的props,dispatchProps,stateProps整合出来
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
// 因为handleNewState执行的大前提是pure为true,因此有必要判断一下先后来自store的state是否变化
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
const stateChanged = !areStatesEqual(nextState, state)
state = nextState
ownProps = nextOwnProps
// 依据不一样的状况,调用不一样的函数
if (propsChanged && stateChanged) return handleNewPropsAndNewState() // 当组件本身的props和注入的store中的某些state同时变化时,调用handleNewPropsAndNewState()获取最新的props
if (propsChanged) return handleNewProps() // 仅当组件本身的props变化时,调用handleNewProps来获取最新的props,此时的props包括注入的props,组件自身的props,和dpspatch内的函数
if (stateChanged) return handleNewState() // 仅当注入的store中的某些state变化时,调用handleNewState()获取最新的props, 此时的props包括注入的props,组件自身的props,和dpspatch内的函数
// 若是都没变化,直接返回先前缓存的mergedProps,而且在以上三个函数中,都分别用闭包机制对数据作了缓存
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
// 第一次渲染,调用handleFirstCall,以后的action派发行为会触发handleSubsequentCalls
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
const mapStateToProps = initMapStateToProps(dispatch, options) // 这里是wrapMapToProps.js中wrapMapToPropsFunc函数的柯里化调用,是改造
// 以后的mapStateToProps, 在下边返回的函数内还会再调用一次
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
// 验证mapToProps函数,有错误时给出提醒
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
)
}
// 根据是否传入了pure,决定计算新props的方式,默认为true
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
复制代码
至此,咱们搞明白了mapToProps函数是在何时执行的。再来回顾一下这部分的问题:如何向组件中注入state和dispatch,让咱们从头梳理一下:
传入mapToProps
首先,在connect的时候传入了mapStateToProps,mapDispatchToProps,mergeProps。再联想一下用法,这些函数内部能够接收到state或dispatch,以及ownProps,它们的返回值会传入组件的props。
基于mapToProps生成selector
须要根据ownProps决定是否要依据其变化从新计算这些函数的返回值,因此会以这些函数为基础,生成代理函数(proxy),代理函数的执行结果就是selector,上边挂载了dependsOnOwnProps属性,因此在selectorFactory内真正执行的时候,才有什么时候才去从新计算的依据。
将selector的执行结果做为props传入组件
这一步在connectAdvanced函数内,建立一个调用selectorFactory,将store以及初始化后的mapToProps函数和其余配置传进去。selectorFactory内执行mapToProps(也就是selector),获取返回值,最后将这些值传入组件。
大功告成
React-Redux的更新机制也是属于订阅发布的模式。并且与Redux相似,一旦状态发生变化,调用listener更新页面。让咱们根据这个过程抓取关键点:
更新谁?
回想一下平时使用React-Redux的时候,是否是只有被connect过而且传入了mapStateToProps的组件,会响应store的变化? 因此,被更新的是被connect过的组件,而connect返回的是connectAdvanced,而且而且connectAdvanced会返回咱们传入的组件, 因此本质上是connectAdvanced内部依据store的变化更新自身,进而达到更新真正组件的目的。
订阅的更新函数是什么? 这一点从connectAdvanced内部订阅的时候能够很直观地看出来:
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
复制代码
订阅的函数是checkForUpdates
,重要的是这个checkForUpdates作了什么,能让组件更新。在connectAdvanced中使用useReducer内置了一个reducer,这个函数作的事情就是在前置条件(状态变化)成立的时候,dispatch一个action,来触发更新。
如何判断状态变化? 这个问题很好理解,由于每次redux返回的都是一个新的state。直接判断先后的state的引用是否相同,就能够了
connectAdvanced是一个比较重量级的高阶函数,上边大体说了更新机制,但不少具体作法都是在connectAdvanced中实现的。源码很长,逻辑有一些复杂,我写了详细的注释。看的过程须要思考函数之间的调用关系以及目的,每一个变量的意义,带着上边的结论,相信不难看懂。
// 这是保留组件的静态方法的库
import hoistStatics from 'hoist-non-react-statics'
import React, {
useContext,
useMemo,
useEffect,
useLayoutEffect,
useRef,
useReducer
} from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'
import Subscription from '../utils/Subscription'
import { ReactReduxContext } from './Context'
const EMPTY_ARRAY = []
const NO_SUBSCRIPTION_ARRAY = [null, null]
// 内置的reducer
function storeStateUpdatesReducer(state, action) {
const [, updateCount] = state
return [action.payload, updateCount + 1]
}
const initStateUpdates = () => [null, 0]
// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
// useLayoutEffect in the browser. We need useLayoutEffect because we want
// `connect` to perform sync updates to a ref to save the latest props after
// a render is actually committed to the DOM.
// 本身对于以上英文注释的意译:
// 当在服务端环境使用useLayoutEffect时候,react会发出警告,为了解决此问题,须要在服务端使用useEffect,浏览器端使用useLayoutEffect。
// useLayoutEffect会在全部的DOM变动以后同步调用传入其中的回调(effect),
// 因此在浏览器环境下须要使用它,由于connect将会在渲染被提交到DOM以后,再同步更新ref来保存最新的props
// ReactHooks文档对useLayoutEffect的说明:在浏览器执行绘制以前,useLayoutEffect 内部的更新计划将被同步刷新。
// useEffect的effect将在每轮渲染结束后执行,useLayoutEffect的effect在dom变动以后,绘制以前执行。
// 这里的effect作的是更新工做
// 在服务端渲染的时候页面已经出来了,有可能js还未加载完成。
// 因此须要在SSR阶段使用useEffect,保证在页面由js接管后,若是须要更新了,再去更新。
// 而在浏览器环境则不存在这样的问题
// 根据是否存在window肯定是服务端仍是浏览器端
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect
export default function connectAdvanced(
selectorFactory,
// options object:
{
// 获取被connect包裹以后的组件名
getDisplayName = name => `ConnectAdvanced(${name})`,
// 为了报错信息的显示
methodName = 'connectAdvanced',
// 直接翻译的英文注释:若是被定义, 名为此值的属性将添加到传递给被包裹组件的 props 中。它的值将是组件被渲染的次数,这对于跟踪没必要要的从新渲染很是有用。默认值: undefined
renderCountProp = undefined,
// connect组件是否应响应store的变化
shouldHandleStateChanges = true,
// 使用了多个store的时候才须要用这个,目的是为了区分该获取哪一个store
storeKey = 'store',
// 若是为 true,则将一个引用存储到被包裹的组件实例中,
// 并经过 getWrappedInstance()获取到。
withRef = false,
// 用于将ref传递进来
forwardRef = false,
// 组件内部使用的context,用户可自定义
context = ReactReduxContext,
// 其他的配置项,selectorFactory应该会用到
...connectOptions
} = {}
) {
//省略了一些报错的逻辑
// 获取context
const Context = context
return function wrapWithConnect(WrappedComponent) {
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
// 定义selectorFactoryOptions,为构造selector作准备
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
const { pure } = connectOptions
/* 调用createChildSelector => createChildSelector(store)(state, ownProps)
createChildSelector返回了selectorFactory的带参调用,而selectorFactory其实是其内部根据options.pure返回的
impureFinalPropsSelectorFactory 或者是 pureFinalPropsSelectorFactory的调用,而这两个函数须要的参数是(state, ownProps)
*/
function createChildSelector(store) {
// 这里是selectorFactory.js中finalPropsSelectorFactory的调用,传入dispatch,和options
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
// 根据是不是pure模式来决定是否须要对更新的方式作优化,pure在这里的意义相似于React的PureComponent
const usePureOnlyMemo = pure ? useMemo : callback => callback()
function ConnectFunction(props) {
// props变化,获取最新的context,forwardedRef以及组件其余props
const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {
const { context, forwardedRef, ...wrapperProps } = props
return [context, forwardedRef, wrapperProps]
}, [props])
// propsContext或Context发生变化,决定使用哪一个context,若是propsContext存在则优先使用
const ContextToUse = useMemo(() => {
// 用户可能会用自定义的context来代替ReactReduxContext,缓存住咱们应该用哪一个context实例
// Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.
// Memoize the check that determines which context instance we should use.
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
// 经过上层组件获取上下文中的store
// 当上层组件最近的context变化的时候,返回该context的当前值,也就是store
const contextValue = useContext(ContextToUse)
// store必须存在于prop或者context中
// 判断store是不是来自props中的store
const didStoreComeFromProps = Boolean(props.store)
// 判断store是不是来自context中的store
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
// 从context中取出store,准备被selector处理以后注入到组件。优先使用props中的store
const store = props.store || contextValue.store
// 仅当store变化的时候,建立selector
// childPropsSelector调用方式: childPropsSelector(dispatch, options)
const childPropsSelector = useMemo(() => {
// selector的建立须要依赖于传入store
// 每当store变化的时候从新建立这个selector
return createChildSelector(store)
}, [store])
const [subscription, notifyNestedSubs] = useMemo(() => {
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
// 若是store是从props中来的,就再也不传入subscription实例,不然使用context中传入的subscription实例
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
// contextValue就是store,将store从新覆盖一遍,注入subscription,这样被connect的组件在context中能够拿到subscription
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
// 若是组件是直接订阅到来自props中的store,就直接使用来自props中的context
return contextValue
}
// Otherwise, put this component's subscription instance into context, so that // connected descendants won't update until after this component is done
// 意译:
// 若是store是从context获取的,那么将subscription放入上下文,
// 为了保证在component更新完毕以前被connect的子组件不会更新
return {
...contextValue,
subscription
}
}, [didStoreComeFromProps, contextValue, subscription])
// 内置reducer,来使组件更新,在checkForUpdates函数中会用到,做为更新机制的核心
const [
[previousStateUpdateResult],
forceComponentUpdateDispatch
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
if (previousStateUpdateResult && previousStateUpdateResult.error) {
throw previousStateUpdateResult.error
}
// Set up refs to coordinate values between the subscription effect and the render logic
/*
* 官方解释:
* useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。
* 返回的 ref 对象在组件的整个生命周期内保持不变。
*
* ref不只用于DOM,useRef()的current属性能够用来保存值,相似于类的实例属性
*
* */
const lastChildProps = useRef() // 组件的props,包括来自父级的,store,dispatch
const lastWrapperProps = useRef(wrapperProps) // 组件自己来自父组件的props
const childPropsFromStoreUpdate = useRef() // 标记来自store的props是否被更新了
const renderIsScheduled = useRef(false) // 标记更新的时机
/*
* actualChildProps是真正要注入到组件中的props
* */
const actualChildProps = usePureOnlyMemo(() => {
// Tricky logic here:
// - This render may have been triggered by a Redux store update that produced new child props
// - However, we may have gotten new wrapper props after that
// If we have new child props, and the same wrapper props, we know we should use the new child props as-is.
// But, if we have new wrapper props, those might change the child props, so we have to recalculate things.
// So, we'll use the child props from store update only if the wrapper props are the same as last time. /* * 意译: * 这个渲染将会在store的更新产生新的props时候被触发,然而,咱们可能会在这以后接收到来自父组件的新的props,若是有新的props, * 而且来自父组件的props不变,咱们应该依据新的child props来更新。可是来自父组件的props更新也会致使总体props的改变,不得不从新计算。 * 因此只在新的props改变而且来自父组件的props和上次一致(下边代码中的判断条件成立)的状况下,才去更新 * * 也就是说只依赖于store变更引发的props更新来从新渲染 * */ if ( childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current ) { return childPropsFromStoreUpdate.current } return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) // We need this to execute synchronously every time we re-render. However, React warns // about useLayoutEffect in SSR, so we try to detect environment and fall back to // just useEffect instead to avoid the warning, since neither will run anyway. /* * 意译:咱们须要在每次从新渲染的时候同步执行这个effect。可是react将会在SSR的状况放下对于useLayoutEffect作出警告, * 因此useIsomorphicLayoutEffect的最终结果是经过环境判断得出的useEffect或useLayoutEffect。在服务端渲染的时候使用useEffect, * 由于在这种状况下useEffect会等到js接管页面之后再去执行,因此就不会warning了 * */ /* * 总体看上下有两个useIsomorphicLayoutEffect,不一样之处在于它们两个的执行时机。 * * 第一个没有传入依赖项数组,因此effect会在每次从新渲染的时候执行,负责每次从新渲染的 * 时候检查来自store的数据有没有变化,变化就通知listeners去更新 * * 第二个依赖于store, subscription, childPropsSelector。因此在这三个变化的时候,去执行effect。 * 其内部的effect作的事情有别于第一个,负责定义更新函数checkForUpdates、订阅更新函数,便于在第一个effect响应store更新的时候, * 能够将更新函数做为listener执行,来达到更新页面的目的 * * */ useIsomorphicLayoutEffect(() => { lastWrapperProps.current = wrapperProps // 获取到组件本身的props lastChildProps.current = actualChildProps // 获取到注入到组件的props renderIsScheduled.current = false // 代表已通过了渲染阶段 // If the render was from a store update, clear out that reference and cascade the subscriber update // 若是来自store的props更新了,那么通知listeners去执行,也就是执行先前被订阅的this.handleChangeWrapper(Subscription类中), // handleChangeWrapper中调用的是onStateChange,也就是在下边赋值的负责更新页面的函数checkForUpdates if (childPropsFromStoreUpdate.current) { childPropsFromStoreUpdate.current = null notifyNestedSubs() } }) // Our re-subscribe logic only runs when the store/subscription setup changes // 从新订阅仅在store内的subscription变化时才会执行。这两个变化了,也就意味着要从新订阅,由于保证传递最新的数据,因此以前的订阅已经没有意义了 useIsomorphicLayoutEffect(() => { // 若是没有订阅,直接return,shouldHandleStateChanges默认为true,因此默认状况会继续执行 if (!shouldHandleStateChanges) return // Capture values for checking if and when this component unmounts // 当组件卸载的时候,用闭包,声明两个变量标记是否被取消订阅和错误对象 let didUnsubscribe = false let lastThrownError = null // 当store或者subscription变化的时候,回调会被从新执行,从而实现从新订阅 const checkForUpdates = () => { if (didUnsubscribe) { // 若是取消订阅了,那啥都不作 return } // 获取到最新的state const latestStoreState = store.getState() let newChildProps, error try { // 使用selector获取到最新的props newChildProps = childPropsSelector( latestStoreState, lastWrapperProps.current ) } catch (e) { error = e lastThrownError = e } if (!error) { lastThrownError = null } // 若是props没变化,只通知一下listeners更新 if (newChildProps === lastChildProps.current) { /* * 浏览器环境下,useLayoutEffect的执行时机是DOM变动以后,绘制以前。 * 因为上边的useIsomorphicLayoutEffect在这个时机执行将renderIsScheduled.current设置为false, * 因此会走到判断内部,保证在正确的时机触发更新 * * */ if (!renderIsScheduled.current) { notifyNestedSubs() } } else { /* * 若是props有变化,将新的props缓存起来,而且将childPropsFromStoreUpdate.current设置为新的props,便于在第一个 * useIsomorphicLayoutEffect执行的时候可以识别出props确实是更新了 * */ lastChildProps.current = newChildProps childPropsFromStoreUpdate.current = newChildProps renderIsScheduled.current = true // 当dispatch 内置的action时候,ConnectFunction这个组件会更新,从而达到更新组件的目的 forceComponentUpdateDispatch({ type: 'STORE_UPDATED', payload: { latestStoreState, error } }) } } // onStateChange的角色也就是listener。在provider中,赋值为更新listeners。在ConnectFunction中赋值为checkForUpdates // 而checkForUpdates作的工做就是根据props的变化,至关于listener,更新ConnectFunction自身 subscription.onStateChange = checkForUpdates subscription.trySubscribe() // 第一次渲染后先执行一次,从store中同步数据 checkForUpdates() // 返回一个取消订阅的函数,目的是在组件卸载时取消订阅 const unsubscribeWrapper = () => { didUnsubscribe = true subscription.tryUnsubscribe() if (lastThrownError) { throw lastThrownError } } return unsubscribeWrapper }, [store, subscription, childPropsSelector]) // 将组件的props注入到咱们传入的真实组件中 const renderedWrappedComponent = useMemo( () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />, [forwardedRef, WrappedComponent, actualChildProps] ) const renderedChild = useMemo(() => { if (shouldHandleStateChanges) { // If this component is subscribed to store updates, we need to pass its own // subscription instance down to our descendants. That means rendering the same // Context instance, and putting a different value into the context. /* * 意译: 若是这个组件订阅了store的更新,就须要把它本身订阅的实例往下传,也就意味这其自身与其 后代组件都会渲染同一个Context实例,只不过可能会向context中放入不一样的值 再套一层Provider,将被重写的context放入value。 这是什么意思呢?也就是说,有一个被connect的组件,又嵌套了一个被connect的组件, 保证这两个从context中获取的subscription是同一个,而它们可能都会往context中新增长值, 我加了一个,个人子组件也加了一个。最终的context是全部组件的value的整合,而subscription始终是同一个 * */ return ( <ContextToUse.Provider value={overriddenContextValue}> {renderedWrappedComponent} </ContextToUse.Provider> ) } // 依赖于接收到的context,传入的组件,context的value的变化来决定是否从新渲染 return renderedWrappedComponent }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]) return renderedChild } // 根据pure决定渲染逻辑 const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction // 添加组件名 Connect.WrappedComponent = WrappedComponent Connect.displayName = displayName // 若是forwardRef为true,将ref注入到Connect组件,便于获取到组件的DOM实例 if (forwardRef) { const forwarded = React.forwardRef(function forwardConnectRef( props, ref ) { return <Connect {...props} forwardedRef={ref} /> }) forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent return hoistStatics(forwarded, WrappedComponent) } // 保留组件的静态方法 return hoistStatics(Connect, WrappedComponent) } } 复制代码
看完了源码,咱们总体归纳一下React-Redux中被connect的组件的更新机制: 这其中有三个要素必不可少:
connectAdvanced函数内从context中获取store
,再获取subscription
实例(可能来自context或新建立),而后建立更新函数checkForUpdates
, 当组件初始化,或者store、Subscription实例、selector变化的时候,订阅或者从新订阅。在每次组件更新的时候,检查一下store是否变化,有变化则通知更新, 实际上执行checkForUpdates,本质上调用内置reducer更新组件。每次更新致使selector从新计算,因此组件老是能获取到最新的props。因此说,更新机制的最底层 是经过connectAdvanced内置的Reducer来实现的。
至此,围绕经常使用的功能,React-Redux的源码就解读完了。回到文章最开始的三个问题:
如今咱们应该能够明白,这三个问题对应着React-Redux的三个核心概念:
它们协同工做也就是React-Redux的运行机制:Provider将数据放入context,connect的时候会从context中取出store,获取mapStateToProps,mapDispatchToProps,使用selectorFactory生成Selector做为props注入组件。其次订阅store的变化,每次更新组件会取到最新的props。
阅读源码最好的办法是先肯定问题,有目的性的去读。开始的时候我就是硬看,越看越懵,换了一种方式后收获了很多,相信你也是。
欢迎关注个人公众号: 一口一个前端,不按期分享我所理解的前端知识