写在开头
前置知识内容,react、react-redux。
react-redux文档:https://www.redux.org.cn/docs/react-redux/
react-redux源码:https://github.com/reduxjs/react-redux
克隆代码
git clone https://github.com/reduxjs/react-redux.git
本文对应redux版本为redux v6.0.1
一、结构
在react-redux的src目录下可以清楚的看到react-redux有三个文件夹components
(用于Provider组件),connect
(用于暴露出来的connect函数),utils
(JavaScript公共函数)。通过阅读index.js
文件可以清楚的看到redux
导出的5个核心方法
文件 | 功能 | |
---|---|---|
index |
redux的入口文件 | 用法 |
components/Context.js |
提供核心APIcreateStore 根据reducer,preState,applyMiddleware。创建并返回store |
|
components/Provider.js |
提供核心APIcombineReducers ,用于合并拆分的reducer |
|
components/connectAdvanced.js |
提供核心APIbindActionCreators 可以简化dispatch action的调用方法。 |
|
connect/connect.js |
提供核心APIconnect 返回一个高阶组件,是对connectAdvanced的包装。 |
|
connect/mapDispatchToProps.js |
在connectAdvanced() 中被调用,使用了职责链模式处理connect中传入的mapDispatchToProps |
|
connect/mapStateToProps.js |
在connectAdvanced() 中被调用,使用了职责链模式处理connect中传入的mapStateToProps |
|
connect/mergeProps.js |
在connectAdvanced() 中被调用,使用了职责链模式处理connect中传入的mergeProps |
|
connect/selectFactory.js |
内置的action.type。 | |
connect/verifySubselectors.js |
数据校验。 | |
connect/wrapMapToProps.js |
mapStateToProps,mapDispatchToProps中调用。 | |
utils/isPlainObject.js |
判断是否是简单对象。 | |
utils/shallowEqual.js |
判断是否是简单对象。 | |
utils/verifyPlainObject.js |
判断是否是简单对象。 | |
utils/warning.js |
用于输出警告信息。 | |
utils/wrapActionCreators.js |
用于输出警告信息。 |
二、工具文件
2.1 isPlainObject
源码截取
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
本函数与redux源码中的isPlainObject
相同。函数用于判断一个对象是否是一个简单对象,简单对象是指直接使用对象字面量{}
或者new Object()
、Object.create(null)
所创建的对象。jQuery和lodash等JavaScript函数均对此有所实现.redux的老期版本使用了ladash的实现版本,后改为自己实现。
2.2 shallowEqual.js
源码截取
const hasOwn = Object.prototype.hasOwnProperty
function is(x, y) {
// SameValue algorithm
if (x === y) {
// +0 != -0
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
//NaN == NaN
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
if (is(objA, objB)) return true
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
函数使用了fbjs的实现方式,比较传入的值是否相等,比较传入的两个对象是否有相同的属性,属性值。
2.3 verifyPlainObject.js
源码截取:
import isPlainObject from './isPlainObject'
import warning from './warning'
export default function verifyPlainObject(value, displayName, methodName) {
if (!isPlainObject(value)) {
warning(
`${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`
)
}
}
用于校验传入的value参数是否为普通对象。对于非普通对象,调用warning方法进行报错
2.4 warning.js
源码截取:
export default function warning(message) {
/* eslint-disable no-console */
if (typeof console !== 'undefined' && typeof console.error === 'function') {
console.error(message)
}
/* eslint-enable no-console */
try {
// This error was thrown as a convenience so that if you enable
// "break on all exceptions" in your console,
// it would pause the execution at this line.
throw new Error(message)
/* eslint-disable no-empty */
} catch (e) {}
/* eslint-enable no-empty */
}
此函数用于打印错误信息,抛出错误信息。与redux源码中的warning.js相同。使用throw new Error(message)是为了方便调试时中断执行
2.5 wrapActionCreators.js
源码截取:
import { bindActionCreators } from 'redux'
export default function wrapActionCreators(actionCreators) {
return dispatch => bindActionCreators(actionCreators, dispatch)
}
函数接受actionCreators参数。返回一个函数,接受dispatch
参数,返回已经使用bindActionCreators包装好,可以直接执行的actionCreators。
三、npm依赖
用于报错。react-is
包名称 | 作用 |
---|---|
invariant | 接受两个参数,如果第一个是false,那么将抛出错误信息,错误信息的内容是第二个参数 |
react-is | 检验react元素的类型 |
prop-types | 用于prop-type的检验 |
loose-envify |
四、核心文件
3.1 components/connectAdvanced.js
此文件暴露出的是一个connectAdvanced函数,是react-redux直接暴露出的四个方法/组件之一。但是似乎并不常用,在没有浏览源码时,我难以察觉此API的存在,这个函数是react-redux的核心,此文件的逻辑依赖其他文件。请在最后在阅读此部分内容。
首先从头开始分析,函数传入的参数如下。
function connectAdvanced(
selectorFactory,
// options object:
{
// the func used to compute this HOC's displayName from the wrapped component's displayName.
// probably overridden by wrapper functions such as connect()
getDisplayName = name => `ConnectAdvanced(${name})`,
// shown in error messages
// probably overridden by wrapper functions such as connect()
methodName = 'connectAdvanced',
// REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of
// calls to render. useful for watching in react devtools for unnecessary re-renders.
renderCountProp = undefined,
// determines whether this HOC subscribes to store changes
shouldHandleStateChanges = true,
// REMOVED: the key of props/context to get the store
storeKey = 'store',
// REMOVED: expose the wrapped component via refs
withRef = false,
// use React's forwardRef to expose a ref of the wrapped component
forwardRef = false,
// the context consumer to use
context = ReactReduxContext,
// additional options are passed through to the selectorFactory
...connectOptions
} = {}
) {/**code**/}
函数执行中,首先使用错误断言(第一个参数为false。就将第二个参数作为错误内容抛出)插件invariant
进行参数校验。校验参数,规则为renderCountProp必须为undefined,!withRef必须为true,storeKey必须为"store"。源码如下。
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'
)
invariant(
storeKey === 'store',
'storeKey has been removed and does not do anything. ' +
'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: . ' +
'You may also pass a {context : MyContext} option to connect'
)
然后创建一个变量存储 Context。默认值是从ReactContext中取得的。
在使用connect时,就是对此函数的调用。最终返回的正是connectAdvanced函数所返回的wrapWithConnect函数。
下面来分析一下这个wrapWithConnect函数。这个函数接受一个React组件作为参数。函数内部首先使用react-is插件和invariant插件对函数的参数进行了校验。 如果是非正式环境直接抛出错误。 接下来获取传入组件的 displayName,主要用于报错。
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || 'Component'
const displayName = getDisplayName(wrappedComponentName)
接下来
const selectorFactoryOptions = {
...connectOptions,
getDisplayName, 用处不大。用来表示connectAdvanced组件和包含的组件的关系。默认为name=>'ConnectAdvanced(' + name + ')',如果使用connect的话。这个参数会被覆盖为connect(name)。主要用于在selectorFactory中验证抛出warning时使用,会被加入到connectOptions一起传给selectorFactorFactory。
methodName, 当前的名称,默认是`connectAdvanced`,如果使用`connect()`函数,会被覆盖成`connect`
renderCountProp,
shouldHandleStateChanges,
storeKey,
displayName,
wrappedComponentName,
WrappedComponent
}
const { pure } = connectOptions
let OuterBaseComponent = Component
if (pure) {
OuterBaseComponent = PureComponent
}
定义一个参数对象selectorFactoryOptions,并且根据传入的connectOptions.pure
参数来确定返回的高阶组件是Component
还是PureComponent
。
接下来
function makeDerivedPropsSelector() {
let lastProps
let lastState
let lastDerivedProps
let lastStore
let lastSelectorFactoryOptions
let sourceSelector
return function selectDerivedProps(
state,
props,
store,
selectorFactoryOptions
) {
if (pure && lastProps === props && lastState === state) {
return lastDerivedProps
}
if (
store !== lastStore ||
lastSelectorFactoryOptions !== selectorFactoryOptions
) {
lastStore = store
lastSelectorFactoryOptions = selectorFactoryOptions
sourceSelector = selectorFactory(
store.dispatch,
selectorFactoryOptions
)
}
lastProps = props
lastState = state
const nextProps = sourceSelector(state, props)
lastDerivedProps = nextProps
return lastDerivedProps
}
}
定义makeDerivedPropsSelector
函数。函数通过闭包存储state,props,store,selectorFactoryOptions这四个变量来实现每次这四个变更更新后与之前值的对比。
接下来
function makeChildElementSelector() {
let lastChildProps, lastForwardRef, lastChildElement, lastComponent
return function selectChildElement(
WrappedComponent,
childProps,
forwardRef
) {
if (
childProps !== lastChildProps ||
forwardRef !== lastForwardRef ||
lastComponent !== WrappedComponent
) {
lastChildProps = childProps
lastForwardRef = forwardRef
lastComponent = WrappedComponent
lastChildElement = (
)
}
return lastChildElement
}
}
紧接着又定义了一个makeChildElementSelector
函数,具体功能与上一个函数相似。
接下来,定义了React组件。
class Connect extends OuterBaseComponent {
constructor(props) {
super(props)
// 断言 forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey]。检测是否存在redux store,已被移除则抛出错误。
this.selectDerivedProps = makeDerivedPropsSelector()
this.selectChildElement = makeChildElementSelector()
this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(this)
}
indirectRenderWrappedComponent(value) {
// calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
return this.renderWrappedComponent(value)
}
renderWrappedComponent(value) {
invariant(
value,
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a , ` +
`or pass a custom React context provider to and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
// 断言value,提示无法在上下文中找到store。可以将根组件包裹在Provider内,或者将一个自动你故意的contextProvider传入Provider。
const { storeState, store } = value
let wrapperProps = this.props
let forwardedRef
if (forwardRef) {
wrapperProps = this.props.wrapperProps
forwardedRef = this.props.forwardedRef
}
let derivedProps = this.selectDerivedProps(
storeState,
wrapperProps,
store,
selectorFactoryOptions
)
return this.selectChildElement(
WrappedComponent,
derivedProps,
forwardedRef
)
}
render() {
const ContextToUse =
this.props.context &&
this.props.context.Consumer &&
isContextConsumer( )
? this.props.context
: Context
return (
{this.indirectRenderWrappedComponent}
)
}
}
组件内使用了之前的两个函数已确保只有props改变了才会更新组件。在render内可以看到,Consumer也可以通过props直接传入,而不适用Provider。
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName //包装显示名字以利于调试
if (forwardRef) {
// 使用React.forwardRef将refs显式的转发到内部
const forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
return
})
forwarded.displayName = displayName
forwarded.WrappedComponent = WrappedComponent
// 拷贝WrappedComponent的静态方法
return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)
返回被connect包装好的组件。
校验完成后
3.2 components/Context.js
源码截取
import React from 'react'
export const ReactReduxContext = React.createContext(null)
export default ReactReduxContext
此函数主要创建一个React Context。用于存储store。以便于可以在被connect包装额组件中会获取context。
3.3 components/Provider.js
Provider.js的源码非常简单,为一个公共的React组件,主要作用是,将storeS和storeState存储在ReactContext。在组件内部订阅了store变化。在每次action触发时,通过订阅函数的执行来设置当前组件的state,从而引起子组件的更新。下面是源代码
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
constructor(props) {
super(props)
const { store } = props
this.state = {
storeState: store.getState(),
store
}
}
componentDidMount() {
this._isMounted = true
this.subscribe()
}
componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe()
this._isMounted = false
}
componentDidUpdate(prevProps) {
if (this.props.store !== prevProps.store) {
//store变化,重新调用订阅函数。如果之前订阅过就取消之前的订阅
if (this.unsubscribe) this.unsubscribe()
this.subscribe()
}
}
subscribe() {
const { store } = this.props
this.unsubscribe = store.subscribe(() => {
const newStoreState = store.getState()
// 组件没有挂载,直接返回
if (!this._isMounted) {
return
}
this.setState(providerState => {
// If the value is the same, skip the unnecessary state update.
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// Actions might have been dispatched between render and mount - handle those
const postMountStoreState = store.getState()
if (postMountStoreState !== this.state.storeState) {
this.setState({ storeState: postMountStoreState })
}
}
render() {
const Context = this.props.context || ReactReduxContext
return (
{this.props.children}
)
}
}
Provider.propTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
}),
context: PropTypes.object,
children: PropTypes.any
}
export default Provider
3.4 connect/connect.js
connect.js文件最后expoet一个connect函数,这个就是react-redux直接暴露出来的connect函数,也是最常用的核心函数,主要用于将actionCreators和storeState传递给子组件。
源码截取
function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg)
if (result) return result
}
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
}
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
} = {}
) {
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
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 createConnect()
首先解释一下此文件的工具函数match。match函数,接受三个参数,arg:参数,factories:函数数组,name:用于报错洗脑洗。函数内部遍历函数数组,如果返回了结果,那么将这个结果返回,如果遍历结束仍没有结果,则抛出错误。
工具函数strictEqual
主要用于传入的两个参数是否严格相等。
connect函数的代码结构也比较简单,首先定义了一个高阶函数createConnect
,将createConnect()
暴露出来。其中crateConnect函数主要接受五个参数,分别是核心函数connectHOC
,四个默认值:mapStateToPropsFactories
,mapDispatchToPropsFactories
,mergePropsFactories
,selectorFactory
。返回的的函数就是我们使用的connect函数,函数主要接受四个可选参数,具体如下。
function connect(
mapStateToProps, //用于将store的state的数据结构映射到传入组件的props属性
mapDispatchToProps, //用于将dispatch传入子组件的props属性。
mergeProps, // 参数传入前两个参数处理后的props和当前的props,返回一个新的对象,这个新的对象会作为props传入被包装的组件。应用根据props,筛选state数据。把 props 中的某个特定变量与 action creator 绑定在一起。参数默认为Object.assign({}, ownProps, stateProps, dispatchProps)
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {}
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
使用match函数和相应的Factories函数对传入的参数做处理。主要作用是根据传入connect参数的函数的类型,对其进行处理,比如说传入的是函数,传入的是对象,或者说传入的是undefined(不传)。
然后返回connectHOC(connectAdvanced所暴露出的函数)函数的执行结果,connectHOC恰好是返回一个高阶函数wrapWithConnect(WrappedComponent)。这个函数就是我们在日常使用中connect()返回的函数。
3.5 connect/mapDispatchToProps.js
源码截取
import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === 'function'
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant(dispatch => ({ dispatch }))
: undefined
}
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant(dispatch =>
bindActionCreators(mapDispatchToProps, dispatch)
)
: undefined
}
export default [
whenMapDispatchToPropsIsFunction,
whenMapDispatchToPropsIsMissing,
whenMapDispatchToPropsIsObject
]
此文件暴露出一个函数组成的数组,每个函数接受一个参数(connect函数的第二个参数)。根据参数的类型对参数做处理,返回一个统一的函数。
3.6 connect/mapStateToProps.js
源码截取
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
此函数与上一个函数作用相同,主要是针对connect函数传入的第一个参数(mapStateToProps)的类型做处理,类型可以是函数,或者是不传。
3.7 connect/mergeProps.js
import verifyPlainObject from '../utils/verifyPlainObject'
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
export function wrapMergePropsFunc(mergeProps) {
return function initMergePropsProxy(
dispatch,
{ displayName, pure, areMergedPropsEqual }
) {
let hasRunOnce = false
let mergedProps
return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)
if (hasRunOnce) {
if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
mergedProps = nextMergedProps
} else {
hasRunOnce = true
mergedProps = nextMergedProps
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(mergedProps, displayName, 'mergeProps')
}
return mergedProps
}
}
}
export function whenMergePropsIsFunction(mergeProps) {
return typeof mergeProps === 'function'
? wrapMergePropsFunc(mergeProps)
: undefined
}
export function whenMergePropsIsOmitted(mergeProps) {
return !mergeProps ? () => defaultMergeProps : undefined
}
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
此文件主要暴露了四个函数,和一个函数数组。但其中主要的只有两个函数。
defaultMergeProps:函数传入了stateProps,dispatchProps,ownProps三个参数。函数使用es6的对象结构,直接返回了一个新的对象。
wrapMergePropsFunc:由函数名可以理解此函数的作用是处理mergeProps,为函数时的情况。此函数直接返回一个新的函数initMergePropsProxy
,参数为( dispatch, { displayName, pure, areMergedPropsEqual})
,在函数内部定义了两个变量hasRunOnce
,mergedProps
(用于缓存,性能优化)。然后直接返回了一个新的函数mergePropsPropsProxy
,此函数接受了三个参数(stateProps, dispatchProps, ownProps)
。函数首先使用mergeProps函数取得了合并后的结果为nextMergeProps。然后判断是否为第一次执行。不是的话,直接将刚才计算好的nextMergedProps存入变量mergedProps,并将结果返回。在第二次执行此函数时,会进行如下判断
if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
mergedProps = nextMergedProps
whenMergePropsIsFunction:包装了一下wrapMergePropsFunc函数
whenMergePropsIsOmitted:包装了defaultMergeProps函数。
最后直接暴露出一个数组[whenMergePropsIsFunction, whenMergePropsIsOmitted]
3.8 connect/selectFactory.js
源码解析
import verifySubselectors from './verifySubselectors'
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
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
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
function handleNewPropsAndNewState() {
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewProps() {
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
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()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
// TODO: Add more comments
// If pure is true, the selector returned by selectorFactory will memoize its results,
// allowing connectAdvanced's shouldComponentUpdate to return false if final
// props have not changed. If false, the selector will always return a new
// object and shouldComponentUpdate will always return true.
export default function finalPropsSelectorFactory(
dispatch,
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
options.displayName
)
}
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
此函数主要是用在connectAdvanced
函数中被调用。
此模块返回的函数为
(state, ownProps)=>final props
此函数主要是对props进行差异比较,避免不必要的组件更新。从而提高性能。如果内部组件需要。除props和store歪的其他状态,则pure必为false。
option.areStatesEqual默认值为(===),areOwnPropsEqual(默认值为shallowEqual),areStatePropsEqual(默认值为shallowEqual),areMergedPropsEqual(默认值为shallowEqual)
3.9 connect/verifySubSelector.js
部分源码
import warning from '../utils/warning'
function verify(selector, methodName, displayName) {
if (!selector) {
throw new Error(`Unexpected value for ${methodName} in ${displayName}.`)
} else if (
methodName === 'mapStateToProps' ||
methodName === 'mapDispatchToProps'
) {
if (!selector.hasOwnProperty('dependsOnOwnProps')) {
warning(
`The selector for ${methodName} of ${displayName} did not specify a value for dependsOnOwnProps.`
)
}
}
}
export default function verifySubselectors(
mapStateToProps,
mapDispatchToProps,
mergeProps,
displayName
) {
verify(mapStateToProps, 'mapStateToProps', displayName)
verify(mapDispatchToProps, 'mapDispatchToProps', displayName)
verify(mergeProps, 'mergeProps', displayName)
}
此函数传入四个参数,mapStateToProps
,mapDispatchToProps
,mergeProps
,displayName
,主要判断前三个参数是否为false(隐式转换的结果是false),如有则直接抛出错误信息。对于mapStateToProps
,mapDispatchToProps
,如果没有dependsOnOwnProps
属性则抛出错误。
3.10 connect/wrapMapToProps.js
源码截取
import verifyPlainObject from '../utils/verifyPlainObject'
export function wrapMapToPropsConstant(getConstant) {
return function initConstantSelector(dispatch, options) {
const constant = getConstant(dispatch, options)
function constantSelector() {
return constant
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
export function wrapMapToPropsFunc(mapToProps, methodName) {
return function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
proxy.mapToProps = mapToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
if (typeof props === 'function') {
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
}
}
这个函数暴露出了三个函数wrapMapToPropsConstant
,getDependsOnOwnProps
,wrapMapToPropsFunc
。暴露出的这几个函数主要在mapStateToProps
,mapDispatchToprosp
中被调用。
首先来说第一个函数wrapMapToPropsConstant
,找出其使用场景如下。
// 第一种调用
export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
// 第二种调用
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant(dispatch => ({ dispatch }))
: undefined
}
// 第三种调用
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant(dispatch =>
bindActionCreators(mapDispatchToProps, dispatch)
)
: undefined
}
函数主体返回了一个新的函数initConstantSelector
,新函数接受两个参数dispatch和options,根据调用地方的代码
// 第一种
wrapMapToPropsConstant(() => ({}))的结果为
function initConstantSelector(dispatch, options) {
function constantSelector() {
return {}
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
// 第二种
wrapMapToPropsConstant(dispatch => ({ dispatch }))的结果为
function initConstantSelector(dispatch, options) {
function constantSelector() {
return {dispatch:dispatch}
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
// 第三种
wrapMapToPropsConstant(dispatch =>
bindActionCreators(mapDispatchToProps, dispatch))的结果为
function initConstantSelector(dispatch, options) {
function constantSelector() {
return {dispatch:bindActionCreators(mapDispatchToProps, dispatch)}
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
其中mapStateToProps会被作为connectAdvanced函数的第二个参数的initMapStateToProps属性
其中mapStateToProps会被作为initMapDispatchToProps函数的第二个参数的initMapDispatchToProps属性
文件暴露出來的第二个参数wrapMapToPropsFunc。此函数返回了一个新的initProxySelector
函数,函数接受两个参数(dispatch,{displayName})。找出调用此函数的地方。
// 第一种
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
// 第二种
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === 'function'
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
对于第一种来说
wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
调用结果
function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
// allow detectFactoryAndVerify to get ownProps
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
proxy.mapToProps = mapStateToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapStateToProps)
let props = proxy(stateOrDispatch, ownProps)
if (typeof props === 'function') {
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
// 不是生产环境,校验props是否为简单对象。
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, 'mapStateToProps')
return props
}
return proxy
}
其中initProxySelector会被作为connectAdvanced函数的第二个参数的initMapStateToProps属性。
其中initProxySelector会被作为initMapDispatchToProps函数的第二个参数的initMapDispatchToProps属性。
五、总结
以上这些内容就是我对于react-redux的部分理解。
react-redux为我们在react中使用redux进行状态管理提供了方便。react-redux主要暴露出了一个connect函数和Provide组件。,我们只需要去定义connect的函数就可以了,大大的降低了使用redux的成本。
六、扩展链接
- react-redux 源码分析(阿里云中台前端)
- https://www.h3399.cn/201811/633961.html
- https://blog.csdn.net/w178191520/article/details/85163610
- https://segmentfault.com/a/1190000008056652
七、写在最后
从零开始学习react-redux十余天,总体感觉react-redux是非常优美的一个框架。不过由于个人的能力原因,对于源码的某些部分,比较难以理解,源码中告诫函数使用的较多,函数式编程思想很彻底。还有用于处理connect参数的职责链模式,react告诫组件,ref等知识。
文章未完善,将持续修改。
预告下一篇不定时更新文章:react的高阶组件和ref。