react-redux原始碼解析
react-redux作為大型react應用中連結react和redux的連結庫,在很多reat專案中廣泛應用。作為開發者的同學們瞭解reac-redux的原理和原始碼對我們的能力提升具有一定的幫助,所謂知其然知其所以然。
預備知識
react-redux(建議不知道connectAdvanced API,connectd的四個引數不是特別熟的同學先去簡單過一下)
準備工作
老規矩github 找到 react-redux, clone master
本文版本: 6.0.0-beta.3
複製程式碼
當然你也可以選擇看筆者的gitHub專案, 其中包含redux和react-redux的原始碼及詳細的註釋
開始擼碼
開啟專案之後,其他的不多說了,我們直接看src裡面的核心程式碼,從index.js 開始(哦, 這裡插一嘴,react-redux是基於react和redux實現的,可以看一下package.json裡面的peerDependencies)
一如既往的簡單, 暴露出的Provider, connectAdvanced, connect 都有對應的api, 我們先來看看Provider的實現。
Provider實現
我們先想一想reac-redux都幹了什麼,簡單來說用 < Provider >< /Provider>包裝了根元件, 把 store 作為 props 傳遞到每一個被 connect() 包裝的元件,其中state to props的計算過程做了優化。 react本身倡導props的自上而下單項傳遞,但是也提供context支援跨層級傳遞,到這裡,聰明的你是不是聯想到了provider與context有關呢,直接看?原始碼相關注釋以新增
建議先看Provider.propTypes提供了那些props -> constructor -> render -> 宣告週期鉤子方法 -> 總結
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
constructor(props) {
super(props)
// 獲取store
const { store } = props
// 初始化state, storeState為初始的redux state
this.state = {
storeState: store.getState(),
// 儲存init store
store
}
}
componentDidMount() {
// 檔案收索_isMounted, 共三處, componentWillUnmount中賦值為false
// 先假設為標記componentDidMount -> componentWillUnmount中
this._isMounted = true
// 來看下subscribe
this.subscribe()
}
componentWillUnmount() {
// 取消監聽subscribe
if (this.unsubscribe) this.unsubscribe()
// 生命週期結束this._isMounted 賦值為false =>_isMounted假設成立✅
this._isMounted = false
}
componentDidUpdate(prevProps) {
// 如果更新之後,store的引用發生變化
if (this.props.store !== prevProps.store) {
// 如果存在監聽則取消
if (this.unsubscribe) this.unsubscribe()
// 更新storeState
this.subscribe()
}
/*
* 在我的理解中redux的store應該是不變的,主要就是為了提供"全域性變數state"
* 既然redux作者這樣寫,我們來思考一下為什麼
* 一般store應該是這樣 let store = createStore(rootReducer, initState, applyMiddleware(xxx));
* this.props.store !== prevProps.store說明全域性的store發生了變化。既createStore方法的重新呼叫,
* 閱讀過redux原始碼的同學應該知道, 既然是新的createStore, "全域性的state"會發生變化,不再是之前的記憶體空間
* 所有這裡再次呼叫subscribe更新state裡面的storeState
*
* * */
}
// 使用store.subscribe方法,保證storeState的最新
subscribe() {
const { store } = this.props
// 監聽subscribe
this.unsubscribe = store.subscribe(() => {
// 獲取最新的state賦值給newStoreState
const newStoreState = store.getState()
// 不在本次生命週期中return
if (!this._isMounted) {
return
}
this.setState(providerState => {
// If the value is the same, skip the unnecessary state update.
// 如果state是相同的引用, 直接跳過state的更新
if (providerState.storeState === newStoreState) {
return null
}
// 更新當前storeState
return { storeState: newStoreState }
})
})
const postMountStoreState = store.getState()
if (postMountStoreState !== this.state.storeState) {
this.setState({ storeState: postMountStoreState })
}
}
render() {
// ReactReduxContext為預設context, 點過去看一下預設值。 看 -> context.js檔案,createContext引數是null
const Context = this.props.context || ReactReduxContext
// value 為this.state
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
Provider.propTypes = {
// 接受store做為props, 並規定store object中的必要方法,既redux createStore的返回物件
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}),
// 接收自定義context
context: PropTypes.object,
// children, 通常為根容器<app />
children: PropTypes.any
}
export default Provider
/**
* provider總結
*
* provider是react-redux提供是react應用的入口元件, 一般為頂層元件<Provider store={store}><App /></Provider>
* 使用react的context傳遞store, 老版本的provider用的getChildContext方法, 隨著react context的api改變,生產者消費者模式的新api尤然而生,因此
* provider的原始碼已經重構, 提供的context為{ store, storeState: state},state儲存為最新
*
* * */
複製程式碼
Context.js 提供全域性的初始化ReactReduxContext
import React from 'react'
// defaultValue 為 null
export const ReactReduxContext = React.createContext(null)
// export出來,以便提供給provider和consumer使用
export default ReactReduxContext
複製程式碼
connect 實現
先回顧一下connect的使用形式:
connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])(<Xxx / >)
複製程式碼
這裡還要了解一下connectAdvanced(selectorFactory, [connectOptions])方法,它是一個將 React 元件連線到 Redux store 的函式。這個函式是 connect() 的基礎。
由於connect方法內引用檔案,function及function的巢狀比較多,如果看官老爺不想過於在意細節,可以直接看一下connect總結
開始看connect原始碼 -> connect.js,簡單看看結構和引用了哪些東西 ->然後我們從export default開始->遇到呼叫的function再去具體看實現了哪些功能
connect.js
/**
* 先回顧一下connect的引數connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
* 作用:連線 React 元件與 Redux store
* mapDispatchToProps(dispatch, [ownProps])
*
* react-redux 暴露的api, connectAdvanced(selectorFactory, [connectOptions])
* 連線react元件和redux的store, 這個引數是connect的基礎
*/
import connectAdvanced from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
// mapStateToProps(state, ownProps)
// 只要 Redux store 發生改變,mapStateToProps 函式就會被呼叫,
// 或者如果有ownProps引數元件接收到新的props,mapStateToProps同樣會被呼叫
import defaultMapStateToPropsFactories from './mapStateToProps'
// mapStateToProps() 與 mapDispatchToProps() 的執行結果和元件自身的 props 將傳入到這個回撥函式中
import defaultMergePropsFactories from './mergeProps'
// 定製 connector 如 pure = true 等等等...
import defaultSelectorFactory from './selectorFactory'
/**
*
* 這裡先以mapStatetoProps為例子, 其他的同理,簡單瞭解後續用到再看
* @param {*} arg 使用時傳進來的mapStatetoProps
* @param {*} factories array[function, function]
* 預設工廠defaultMapStateToPropsFactories, [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
* @param {*} name string
* @returns function
*/
function match(arg, factories, name) {
// 後到前遍歷factories
for (let i = factories.length - 1; i >= 0; i--) {
// 呼叫factories, 返回值賦值給result, 去看 -> mapStateToProps.js
const result = factories[i](arg)
// result為true返回result, result為(dispatch, options) => function(){...}的function
if (result) return result
}
// 不符合connect方法規則throw Error
return (dispatch, options) => {
throw new Error(
`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
options.wrappedComponentName
}.`
)
}
}
// 判斷物件的索引是否相同
function strictEqual(a, b) {
return a === b
}
// 暴露createConnect, 想必肯定會返回function因為connect是個function
export function createConnect({
// 一些帶有預設值的引數, 我們看下面, 具體用到了在看?
connectHOC = connectAdvanced, // connectAdvanced(selectorFactory, [connectOptions])
mapStateToPropsFactories = defaultMapStateToPropsFactories, // array[function, function]
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
// 返回的connect function
return function connect(
// connect的四個可選引數
mapStateToProps,
mapDispatchToProps,
mergeProps,
// 配置引數
{
pure = true, // 是否就行淺比較的配置
// 判斷是否相同引用的function
areStatesEqual = strictEqual,
// shallowEqual 詳情看下面?
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
// 其他配置項
...extraOptions
} = {}
) {
// mapStateToProps初始化
const initMapStateToProps = match(
// 使用時傳遞的mapStateToProps function
mapStateToProps,
// 預設值 -> 先看match方法, 然後我們來看mapStateToProps.js
mapStateToPropsFactories,
'mapStateToProps'
)
// mapDispatchToProps初始化
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
// mergeProps的初始化
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
// return connectHOC function,將 React 元件連線到 Redux store 的函式
// 先來看看他的引數
// selectorFactory函式返回一個selector函式,根據store state, 展示型元件props,和dispatch計算得到新props,最後注入容器元件
// selectorFactory -> defaultSelectorFactory
// 其實很熟悉react-redux api的同學應該很熟悉connectHOC的引數, 因為他就是connectAdvanced方法啊, 建議先看看api
return connectHOC(selectorFactory, {
// 用於錯位資訊
methodName: 'connect',
// 用Connect包裝getDisplayName
getDisplayName: name => `Connect(${name})`,
// mapStateToProps是否為undefined,shouldHandleStateChanges為false則不監聽store state
shouldHandleStateChanges: Boolean(mapStateToProps),
// selectorFactory需要的幾個引數
initMapStateToProps, // (dispatch, options) => initProxySelector(dispatch, { displayName }){...}
initMapDispatchToProps,
initMergeProps,
pure, // 預設true
// strictEqual, 這裡很容易想到用於判斷this.state是不是都一份引用
areStatesEqual,
// shallowEqual淺比較
// 插個題外話,熟悉react PureComponent的同學應該可以快速反應過來!shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState)
// 不熟悉的同學看過來 -> shallowEqual.js
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// 容錯處理, 其他的配置項
...extraOptions
})
}
}
// connect方法 直接呼叫createConnect
export default createConnect()
複製程式碼
mapStateToProps.js
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
// 當mapStateToProps為function時呼叫wrapMapToPropsFunc
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
// 看wrapMapToPropsFunc -> wrapMapToProps.js
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
// match中的陣列倒著遍歷先看這裡
// 容錯處理, 判斷是否傳遞mapStateToProps引數
export function whenMapStateToPropsIsMissing(mapStateToProps) {
// 如果傳遞了mapStateToProps引數且!mapStateToProps = true返回undefined,此時result為undefined,無效呼叫
// 沒有傳遞mapStateToProps引數或mapStateToProps=false -> wrapMapToPropsConstant
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
// export default是array,元素為function
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
/**
* 小結:
*
* mapstateToprops引數可執行時呼叫wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
* 不傳遞mapstateToprops引數時 wrapMapToPropsConstant(() => ({}))
* 我們接著刨根問底 看->wrapMapToProps.js
*
* * */
複製程式碼
wrapMapToProps.js
import verifyPlainObject from '../utils/verifyPlainObject'
/**
*
* @export mapstateToprops為undefined呼叫
* @param {*} getConstant () => ({})
* @returns function initConstantSelector(dispatch, options)
*/
export function wrapMapToPropsConstant(getConstant) {
// 返回 initConstantSelector
return function initConstantSelector(dispatch, options) {
const constant = getConstant(dispatch, options)
function constantSelector() {
return constant
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}
// 用來判斷是否存在ownProps
// mapStateToProps(state, [ownProps])
export function getDependsOnOwnProps(mapToProps) {
// 不是第一次呼叫直接返回Boolean
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
// 第一次呼叫時mapToProps的dependsOnOwnProps為undefined,直接判斷引數個數
: mapToProps.length !== 1
}
/**
* @export mapstateToprops傳遞時呼叫
* @param {*} mapToProps 使用connect是傳遞的mapStateToProprs
* @param {*} methodName 名稱 methodName = 'mapStatetoProps' || 'mapDispatchToProps'
* @returns 返回initProxySelector(dispatch, { displayName })
*/
export function wrapMapToPropsFunc(mapToProps, methodName) {
// 終於找到你!! 返回initProxySelector function, 這個返回值會賦值給initMapStateToProps(當然還有initDispatchToProps)
return function initProxySelector(dispatch, { displayName }) {
// 定義proxy function,且作為返回值
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps // mapStateToProps計算是否依賴元件的props
? proxy.mapToProps(stateOrDispatch, ownProps) // 返回proxy.mapToProps,繼續看一下他是什麼鬼?
: proxy.mapToProps(stateOrDispatch)
}
// dependsOnOwnProps標記執行依賴元件的props為true
proxy.dependsOnOwnProps = true
// detectFactoryAndVerify為返回的function
// 梳理一下,目前呼叫鏈是這樣的
// const initMapStateToProps = initProxySelector(dispatch, { displayName })=>
// mapToPropsProxy(stateOrDispatch, ownProps) => detectFactoryAndVerify(stateOrDispatch, ownProps)
// detectFactoryAndVerify賦值給proxy.mapToProps
// 第一次呼叫 mapToPropsProxy時返回detectFactoryAndVerify(stateOrDispatch, ownProps)
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
// 呼叫的時候 mapToPropsfunction 賦值給 proxy.mapToProps
// 也就是第一次除了呼叫到proxy.mapToProps之後, 以後在呼叫到proxy.mapToProps的時候則使用傳遞的mapToProps function
proxy.mapToProps = mapToProps
// 重新判斷 dependsOnOwnProps(第一次預設true)
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
// 定義props為proxy(stateOrDispatch, ownProps)
// 先看下執行順序 第一次呼叫initProxySelector() => proxy() =>
// 此時 proxy.mapToProps = detectFactoryAndVerify()
// 再次呼叫 proxy(stateOrDispatch, ownProps)時 返回值為傳遞的mapToProps(...args),也就是我們react元件需要的props
let props = proxy(stateOrDispatch, ownProps)
// 如果props為function再次執行
if (typeof props === 'function') {
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
// 非production環境檢查
if (process.env.NODE_ENV !== 'production')
// verifyPlainObject是utils方法, 如果不是純物件,丟擲warning
verifyPlainObject(props, displayName, methodName)
// 返回最終的props
return props
}
return proxy
}
}
複製程式碼
mergeProps.js和mapDisptchToPtops.js同理,返回一個array[function,function],對引數做容錯處理,返回一個initProxySelector function() => proxy => props, 具體細節後面介紹
shallowEqual
shallowEqual通常用來對object做淺比較,經常會出現在react應用中, 配合shouldComponentUpdate做效能優化, 如果熟悉PureComponent原理同學,那應該知道這段程式碼
!shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState)
複製程式碼
shallowEqual.js
// 物件自身屬性中是否具有指定的屬性
const hasOwn = Object.prototype.hasOwnProperty
// 判斷兩個值是否是相同的值
function is(x, y) {
// SameValue algorithm
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
// 淺比較 只會比較到兩個物件的 ownProperty 是否符合 Object.is 判等,不會遞迴地去深層比較
// shallowEqual({x:{}},{x:{}}) // false
// shallowEqual({x:1},{x:1}) // true
export default function shallowEqual(objA, objB) {
// 相同值直接返回true,shallowEqual返回值為boolean
if (is(objA, objB)) return true
// 在objA和objB不是相同值的前提下, 如果objA,objB為null或非object可以判定返回false
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}
// 定義objA,objB的key陣列
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
// length不同說明object不同
if (keysA.length !== keysB.length) return false
// 迴圈遍歷keysA
for (let i = 0; i < keysA.length; i++) {
// 如果objB不含有objA的key且objA與objB的value不同, 返回false
if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
// 如果通過了for迴圈, 說明objA, objB中的第一層key和value都相同,恭喜通過
return true
}
複製程式碼
connect總結
由於connect函式設計的方法過多,我們在這裡簡化一下connect的程式碼(以下為原始碼的精簡虛擬碼)
// 首先為以引數的形式為connect注入一些方法
export function createConnect({
...
// connectAdvanced為react-redux暴露出的api
connectHOC = connectAdvanced,
...
} = {}) {
// connect方法
return function connect(
// 接受的四個引數
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true, // 是否就行淺比較的配置
strictEqual, // 判斷object引用, strictEqual(a, b)=> a === b
shallowEqual, // 淺比較,上面介紹了
...extraOptions // 其他配置項
} = {}
) {
// 一系列的方法執行,對三個引數的型別做了容錯處理
// 分別初始化了各自的引數mapStateToProps,mapDispatchToProps,mergeProps,注入了一些內部的預設引數和方法
// 他們大致是這樣的function:
// (dispatch, options) => initProxySelector() => mapToPropsProxy() => props
const initMapStateToProps = match(...args)
const initMapDispatchToProps = match(...args)
const initMergeProps = match(...args)
// 返回值由執行connectAdvanced獲取,並傳入初始化的initMapStateToProps等引數和pure等配置項
return connectAdvanced(selectorFactory, {
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
...extraOptions
})
}
}
// 直接執行createConnect方法返回connect
export default createConnect()
複製程式碼
connectAdvanced高階函式
從connect()(<A/ >)中不難看出connect的返回值為一個高階元件,包裝當前的react元件,返回一個增強props的元件,在執行connect時會執行connectAdvanced,接下來我們不得不看下connectAdvanced方法的實現,先介紹一下connectAdvanced -> ?看註釋
connectAdvanced.js
// hoist-non-react-statics元件,這個元件會自動把所有繫結在物件上的非React靜態(static)方法都繫結到新的物件上
import hoistStatics from 'hoist-non-react-statics'
// 提示資訊外掛
import invariant from 'invariant'
import React, { Component, PureComponent } from 'react'
import { isValidElementType } from 'react-is'
// context
import { ReactReduxContext } from './Context'
/**
*
* @export 介紹下connectAdvanced
* connect() 的基礎,真正將 React 元件連線到 Redux store 的函式
* @param {*} selectorFactory function selectorFactory(dispatch, factoryOptions)
* 初始化選擇器函式,該選擇器函式是在 connector 元件需要重新計算一個新的 props 時呼叫
* 作為 store 的 state 改變或者接收到一個新的 props 的結果。selector 的結果應該是一個普通物件,
* 作為被包裹的元件的 props 傳遞。如果連續呼叫 selector 都返回與上一次呼叫相同的物件(===),
* 則不會重新渲染該元件。selector 的責任是在適當的時候返回以前的物件。
* @param {*} [{
* getDisplayName = name => `ConnectAdvanced(${name})`, DisplayName
* methodName = 'connectAdvanced',
* renderCountProp = undefined,
* shouldHandleStateChanges = true,
* storeKey = 'store',
* withRef = false,
* context = ReactReduxContext,
* ...connectOptions
* }={}]
* @returns
*/
export default function connectAdvanced(
// function, 使用的時候在介紹selectorFactory.js
selectorFactory,
// options object:
{
// 被包裹的元件的 DisplayName 屬性
getDisplayName = name => `ConnectAdvanced(${name})`,
// 用於顯示錯誤訊息
methodName = 'connectAdvanced',
// 元件是否訂閱 redux store 的 state 更改
shouldHandleStateChanges = true,
renderCountProp = undefined, // 傳遞給內部元件的props鍵,表示render方法呼叫次數
// 可以獲取 store 的 props/context key
storeKey = 'store',
// 如果為 true,則將一個引用儲存到被包裹的元件例項中,並通過 getWrappedInstance() 方法使其可用
withRef = false,
// 看到這個變數我的第一反應是ref的轉發, 不瞭解的同學去看一下React.forwardRef()
forwardRef = false,
// provider的ReactReduxContext方法
context = ReactReduxContext,
...connectOptions
} = {}
) {
// invariant 一個只在development環境的error
// When process.env.NODE_ENV is not production, the message is required.
// 這幾個配置引數別使用的時候會有warnning,直接跳過這部分
invariant(
renderCountProp === undefined,
`renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
)
invariant(
!withRef,
'withRef is removed. To access the wrapped instance, use a ref on the connected component'
)
const customStoreWarningMessage =
'To use a custom Redux store for specific components, create a custom React context with ' +
"React.createContext(), and pass the context object to React-Redux's Provider and specific components" +
' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
'You may also pass a {context : MyContext} option to connect'
invariant(
storeKey === 'store',
'storeKey has been removed and does not do anything. ' +
customStoreWarningMessage
)
// 定義Context
const Context = context
// 返回react高階元件, WrappedComponent既包裝的react元件
return function wrapWithConnect(WrappedComponent) {
// 引數檢驗
if (process.env.NODE_ENV !== 'production') {
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
)
}
// 元件的displayName,預設Component
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
// 拼接下displayName
const displayName = getDisplayName(wrappedComponentName)
// 定義selectorFactoryOptions物件,包含了connect和connectAdvanced的所有引數
const selectorFactoryOptions = {
// connectOptions為initMapStateToProps,initMapDispatchToProps,pure等引數
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
// pure決定shouldComponentUpdate是否進行shwoEqual
const { pure } = connectOptions
// Component賦值給OuterBaseComponent, 用react高階的繼承
let OuterBaseComponent = Component
// 定義FinalWrappedComponent
let FinalWrappedComponent = WrappedComponent
if (pure) {
// 為true用PureComponent
OuterBaseComponent = PureComponent
}
// 接下來直接看 class Connect extends OuterBaseComponent
//
function makeDerivedPropsSelector() {
// 定義變數, 用來儲存上一次makeDerivedPropsSelector中的值
// 變數語義化我們不難猜出意義
let lastProps
let lastState
let lastDerivedProps
let lastStore
let sourceSelector
return function selectDerivedProps(state, props, store) { // props為父元件的props
// pure為true時,props和state的引用都沒有變化, 直接返回lastDerivedProps(第一次肯定不會成立)
// 這裡不難理解, 就是要求你用"純"的state和props
if (pure && lastProps === props && lastState === state) {
return lastDerivedProps
}
if (store !== lastStore) {
// store賦值lastStore, 更新lastStore
// 除第一次呼叫外一般不會
lastStore = store
// selectorFactory為connect傳入的function,預設值來自selsctorFactory.js的export default
sourceSelector = selectorFactory(
store.dispatch,
selectorFactoryOptions // 所有引數集合
)
}
// props賦值給lastProps
lastProps = props
// state賦值給lastState
lastState = state
// 呼叫sourceSelector參入redux state和props, 得到最新的props
// 不難看出selectorFactory的返回值為一個function, 目前我們可以猜測到
// selsctorFactory.js的export default function 大體結構是這樣(dispatch, selectorFactoryOptions)=>(state, props) => newProps
const nextProps = sourceSelector(state, props)
// 新舊props引用相同
if (lastDerivedProps === nextProps) {
// 直接返回
return lastDerivedProps
}
// 新舊props引用不相同, nextProps賦值給lastDerivedProps
lastDerivedProps = nextProps
// 返回lastDerivedProps
return lastDerivedProps
// 最後我們去看selsctorFactory.js到底如何合併的state和props
}
}
function makeChildElementSelector() {
// 定義props, ref, element變數
let lastChildProps, lastForwardRef, lastChildElement
// 返回function
return function selectChildElement(childProps, forwardRef) {
// 判斷新舊props, hre, elelment是否相同
if (childProps !== lastChildProps || forwardRef !== lastForwardRef) {
// 如果不同重新賦值
lastChildProps = childProps
lastForwardRef = forwardRef
lastChildElement = (
// return FinalWrappedComponent, 改變props和ref
<FinalWrappedComponent {...childProps} ref={forwardRef} />
)
}
// react元件
return lastChildElement
}
}
class Connect extends OuterBaseComponent {
constructor(props) {
super(props)
invariant(
forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey],
'Passing redux store in props has been removed and does not do anything. ' +
customStoreWarningMessage
)
// 新增selectDerivedProps和selectChildElement方法
// selectDerivedProps為function是makeDerivedPropsSelector的返回值
this.selectDerivedProps = makeDerivedPropsSelector()
// selectChildElement為function
this.selectChildElement = makeChildElementSelector()
// bind this
this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
}
// value為context,既provider中的{storeState: store.getState(),store}
renderWrappedComponent(value) {
invariant(
value,
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
// 獲取redux state和store
const { storeState, store } = value
// 定義wrapperProps為this.props
let wrapperProps = this.props
let forwardedRef
// forwardRef為真時, Connect元件提供了forwardedRef = {ref}
if (forwardRef) {
// wrapperProps為props中的wrapperProps
wrapperProps = this.props.wrapperProps
// forwardedRef賦值為props的forwardedRef, 傳遞的是ref
// 用於傳遞給子元件WrappedComponent既let FinalWrappedComponent = WrappedComponent中的FinalWrappedComponent
forwardedRef = this.props.forwardedRef
}
// 匯出props
let derivedProps = this.selectDerivedProps(
storeState,
wrapperProps,
store
)
// 返回最終的元件,傳入最終的props和ref -> 看selectChildElement發放
return this.selectChildElement(derivedProps, forwardedRef)
}
render() {
// 預設情況下公用的ReactReduxContext
const ContextToUse = this.props.context || Context
return (
// <Privoder />的消費者
<ContextToUse.Consumer>
{this.renderWrappedComponent}
</ContextToUse.Consumer>
)
}
}
// 相當於外掛
// 包裝的元件賦值給Connect.WrappedComponent
Connect.WrappedComponent = WrappedComponent
// 新增displayName
Connect.displayName = displayName
// forwardRef為true時
if (forwardRef) {
// 使用react.forwardRef為connect生成的元件的父元件提供孫子(傳遞給connect的元件)元件
const forwarded = React.forwardRef(function forwardConnectRef(
props,
ref
) {
return <Connect wrapperProps={props} forwardedRef={ref} />
})
// 此時connect()(<xx />)的生成元件為forwarded, 從新掛載displayName和WrappedComponent
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
return hoistStatics(forwarded, WrappedComponent)
}
// 將子元件的非React的static(靜態)屬性或方法合併到父元件
// 返回擴充過props屬性的Connect元件
return hoistStatics(Connect, WrappedComponent)
}
}
複製程式碼
connectAdvanced返回一個react高階元件, 根據pure,forwardRef配置決定是否採用PureComponent和ref的轉移, 通過selectDerivedProps放法生成最終的props,傳遞給最終返回的react元件,最後我們去看selsctorFactory.js到底如何合併的state和props
selectorFactory
selectorFactory函式返回一個selector函式,根據store state, 展示型元件props和dispatch計算得到新props,最後注入容器元件中
selectorFactory.js實現
import verifySubselectors from './verifySubselectors'
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
return function impureFinalPropsSelector(state, ownProps) {
// 執行mergePropsProxy,返回修改後的props
return mergeProps(
mapStateToProps(state, ownProps), // mapStateToProps執行結果
mapDispatchToProps(dispatch, ownProps), // mapDispatchToProps的執行結果
ownProps // 自身的props
)
}
}
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
// areStatesEqual判斷是否是相同的引用
// areOwnPropsEqual, areStatePropsEqual為shallowEqual
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
// hasRunAtLeastOnce標記第一次執行
// 先看return的function,再看其他的function都做了什麼
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
// 第一次執行時
function handleFirstCall(firstState, firstOwnProps) {
// 直接賦值以下結果
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
// hasRunAtLeastOnce標記為true
hasRunAtLeastOnce = true
// 返回mergedProps
return mergedProps
}
function handleNewPropsAndNewState() {
// 獲取當前新的的state
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
// 返回mergedProps function內部為新的object
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// 自身props改變
function handleNewProps() {
// dependsOnOwnProps之前介紹過,判斷是否有第一個引數ownprops
// 如果存在需要重新執行,獲取新的stateProps和mapDispatchToProps,因為自身的props改變了
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// redux state改變
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
// 淺比較nextStateProps和stateProps
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
// 更新stateProps
stateProps = nextStateProps
//statePropsChanged為ture,淺比較失敗,mergedProps需要重新計算,mergedProps返回新物件
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
// 執行的時候ownProps淺比較
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
// 比較redux state的引用
const stateChanged = !areStatesEqual(nextState, state)
// nextState賦值給state
state = nextState
// nextOwnProps賦值給onwProps
ownProps = nextOwnProps
// props && state change
// 看不同情況對應的return function
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
// props change
if (propsChanged) return handleNewProps()
// state change
if (stateChanged) return handleNewState()
// propsChanged, stateChanged為true認為props,state沒有改變,return mergedProps
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) { // state props
return hasRunAtLeastOnce // 預設值為false
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
// 找到export default
/**
*
*
* @export
* @param {*} dispatch // store.dispatch
* @param {*} {
* initMapStateToProps // 結構initProxySelector(dispatch, { displayName }) => proxy
* initMapDispatchToProps, // 結構 initMergePropsProxy(dispatch, options) => mergePropsProxy
* initMergeProps,
* ...options 其他配置
* }
* @returns selectorFactory function
*/
// finalPropsSelectorFactory和我們的設想結構一致
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
// 呼叫initProxySelector得到proxy function, proxy包含mapToProps, dependsOnOwnProps屬性
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
// mergePropsProxy為function
// 返回值為connect(mapstate,mapdispatch,function mergeProps(){})()中mergeProps的返回值
const mergeProps = initMergeProps(dispatch, options)
// 非production環境檢驗 mapStateToProps,mapDispatchToProps,mergeProps
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
)
}
// pure為true時表示selectorFactory的返回值快取, 根據當前的redux state和ownProps的變化儘量做最出小的改變
// 詳情看pureFinalPropsSelectorFactory
// 否則返回新物件
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
// 執行selectorFactory
// selectorFactory為工廠函式,返回selector
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps, // function mergePropsProxy
dispatch,
options
)
}
複製程式碼
mergedProps實現
import verifyPlainObject from '../utils/verifyPlainObject'
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
// 返回一個新物件
return { ...ownProps, ...stateProps, ...dispatchProps }
}
export function wrapMergePropsFunc(mergeProps) {
// initMergeProps
return function initMergePropsProxy(
dispatch,
{ displayName, pure, areMergedPropsEqual }
) {
// 第一次執行,設定為false
let hasRunOnce = false
let mergedProps
return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
// mergeProps的返回結果
const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)
if (hasRunOnce) {
// pure為fales 或者mergedProps為null
if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
mergedProps = nextMergedProps
} else {
// 不是第一次執行,hasRunOnce標記為false
hasRunOnce = true
// nextMergedProps賦值給mergedProps
mergedProps = nextMergedProps
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(mergedProps, displayName, 'mergeProps')
}
// 返回修改後的props
return mergedProps
}
}
}
export function whenMergePropsIsFunction(mergeProps) {
// 如果mergeProps為true且是function, 呼叫wrapMergePropsFunc返回function initMergePropsProxy(){}
return typeof mergeProps === 'function'
? wrapMergePropsFunc(mergeProps)
: undefined
}
export function whenMergePropsIsOmitted(mergeProps) {
// mergeProps !== true retun () => { ...ownProps, ...stateProps, ...dispatchProps }
return !mergeProps ? () => defaultMergeProps : undefined
}
// 後向前執行
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
/**
* [mergeProps(stateProps, dispatchProps, ownProps): props] (Function):
* 如果指定了這個引數,mapStateToProps() 與 mapDispatchToProps() 的執行結果和元件自身的 props 將傳入到這個回撥函式中。
* 該回撥函式返回的物件將作為 props 傳遞到被包裝的元件中
*/
複製程式碼