注重:文章很长,只想相识逻辑而不深切的,可以直接跳到
总结部份。
初识
起首,从它暴露对外的API
最先
ReactReduxContext
/*
供应了 React.createContext(null)
*/
Provider
/*
一个贮存数据的组件,衬着了ContextProvider,内部挪用redux中store.subscribe
定阅数据,每当redux中的数据更改,比较新值与旧值,推断是不是从新衬着
*/
connect
/*
一个高阶组件,第一阶传入对数据处置惩罚要领,第二阶传入要衬着的组件
内部处置惩罚了:
1. 对参数的搜检
2. 对传入的数据处置惩罚要领举行处置惩罚
(没传怎样处置惩罚,传了供应什么参数,传的范例差别怎样处置惩罚,效果怎样比较等等)
3. 静态要领转移
4. 对衬着组件的通报(通报给connectAdvanced)
*/
connectAdvanced
/*
保留每一次实行的数据,实行connect定义的计划和逻辑,新旧数据对照(全等对照),衬着组件
这里作为公然API,假如我们去运用,那末connect内里的逻辑就需要我们自定义了。
*/
如今对它的也许事变范围有相识后,我们可以最先沿着实行递次剖析。
抽丝
Provider
我们运用时,当写完了redux的reducer
, action
, bindActionCreators
, combineReducers
, createStore
这一系列内容后,
我们得到了一个store
会先运用<Provider store={store}
包裹住根组件。
这时候,Provider
组件最先事变
componentDidMount() {
this._isMounted = true
this.subscribe()
}
第一次加载,需要实行subscribe
subscribe
是什么呢,就是对redux
的store
实行subscribe
一个自定义函数,
如许,每当数据更改,这个函数便会实行
subscribe() {
const { store } = this.props
// redux 的 store 定阅
// 定阅后,每当state转变 则自动实行这个函数
this.unsubscribe = store.subscribe(() => {
// store.getState() 猎取最新的 state
const newStoreState = store.getState()
// 组件未加载,取消
if (!this._isMounted) {
return
}
// 比较state是不是相称,全等的不更新
this.setState(providerState => {
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
/* ... */
}
看到吗,这个自定义函数异常简朴,每次收到数据,举行全等比较,不等则更新数据。
这个组件的另2个性命周期函数:
componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe()
this._isMounted = false
}
componentDidUpdate(prevProps) {
// 比较store是不是相称,假如相称则跳过
if (this.props.store !== prevProps.store) {
// 取消定阅之前的,再定阅如今的(由于数据(store)差别了)
if (this.unsubscribe) this.unsubscribe()
this.subscribe()
}
}
这2段的意义就是,每当数据变了,就取消上一次数据的定阅,在定阅本次的数据,
当要烧毁组件,取消定阅。
一段题外话(可跳过):
这个逻辑用
Hooks
的useEffect
几乎圆满婚配!useEffect(()=>{ subscribe() return ()=>{ unSubscribe() } },props.data)
这段的意义就是,当
props.data
发作转变,实行unSubscribe()
,再实行subscribe()
。逻辑完全一致有无!
末了的render
:
这里Context
就是React.createContext(null)
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
到这里我称为react-redux
的第一阶段。
一个小总结,第一阶段就做了1件事:
定义了Provider
组件,内部定阅了store
。
connect
到主菜了,先看它的export
export default createConnect()
一看,我们应该有个猜想,这货createConnect
是个高阶函数。
看看它的参数吧。
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
/* ... */
}
题外话:一个编写默许对象内部含有默许值的要领
function a({x=1,y=2}={}){} a() // x:1,y:2 a({}) // x:1,y:2 a({x:2,z:5}) //x:2,y:2
这里先申明一下它的参数,背面读起来会很顺。
connectHOC: 一个主要组件,用于实行已肯定的逻辑,衬着终究组件,背面会详细说。
mapStateToPropsFactories: 对 mapStateToProps 这个传入的参数的范例挑选一个适宜的要领。
mapDispatchToPropsFactories: 对 mapDispatchToProps 这个传入的参数的范例挑选一个适宜的要领。
mergePropsFactories: 对 mergeProps 这个传入的参数的范例挑选一个适宜的要领。
selectorFactory: 以上3个只是简朴的返回另一个适宜的处置惩罚要领,它则实行这些处置惩罚要领,而且对效果定义了怎样比较的逻辑。
可以有点绕,但react-redux
就是这么一个个高阶函数构成的,selectorFactory
背面会详细说。
起首我们再次肯定这3个名字很长,现实很简朴的函数(源码这里不放了)
mapStateToPropsFactories
mapDispatchToPropsFactories
mergePropsFactories
它们只是推断了参数是不是存在,是什么范例,而且返回一个适宜的处置惩罚要领,它们并没有任何处置惩罚逻辑。
- 举个例子:
const MyComponent=connect((state)=>state.articles})
这里我只定义了
mapStateToProps
,而且是个function
,那末mapStateToPropsFactories
就会返回一个
处置惩罚function
的要领。我没有定义
mapDispatchToProps
,那末mapDispatchToPropsFactories
检测不到参数,
则会供应一个默许值dispatch => ({ dispatch })
,返回一个处置惩罚非function
(object)的要领。
那末处置惩罚逻辑是谁定义呢?
wrapMapToProps
wrapMapToProps.js
这个文件内部做了以下事变:
- 定义了一个处置惩罚
object
的要领(简朴的返回即可,由于终究目标就是要object)。 - 定义了一个处置惩罚
函数
和高阶函数
(实行2次)的要领,这个要领比上面的庞杂在于它需要检测参数是不是定阅了ownProps
。
检测要领很简朴,就是搜检参数的length
(这里dependsOnOwnProps
是上一次搜检的效果,假如存在则不需要再次搜检)
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
回到connect,继承往下看
export function createConnect({
/* 上面所讲的参数 */
} = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
/* ... */
}
}
已到了我们通报参数的处所,前3个参数意义就不诠释了,末了的参数options
areStatesEqual = strictEqual, // ===比较
areOwnPropsEqual = shallowEqual, // 浅比较
areStatePropsEqual = shallowEqual, // 浅比较
areMergedPropsEqual = shallowEqual, // 浅比较
它们用在selectorFactory
这个比较数据效果的要领内部。
继承往下看
export function createConnect({
/* 上面已讲 */
} = {}) {
return function connect(
/* 上面已讲 */
) {
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
这里定义了3个变量(函数),match
的作用是什么?
以mapStateToProps
举例来说,
由于上面也说了,mapStateToPropsFactories
内里有多个要领,需要找到一个合适mapStateToProps
的,match
就是干这事了。
match
要领内部遍历mapStateToPropsFactories
一切的处置惩罚要领,任何一个要领可以婚配参数mapStateToProps
,便被match
捕捉返回,
假如一个都找不到则报错提醒参数设置毛病。
如今这3个变量定义明确了,都是对应的参数的适宜的处置惩罚要领。
至此,我们已完成了第二阶段,
做个小总结,第二阶段做了哪些事:
-
connect
接收了对参数处置惩罚计划(3个...Factories
)。 -
connect
接收了参数的效果比较计划(selectFactory
) -
connect
接收了参数(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。 - 定义了比较计划(4个
are...Equal
,实在就是全等比较
和浅比较
)。
前2个阶段都是定义阶段,接下来需要我们传入自定义组件,也就是末了一个阶段
connect(...)(Component)
接着看connect
源码
export function createConnect({
/* 上面已讲 */
} = {}) {
return function connect(
/* 上面已讲 */
) {
/* 上面已讲 */
return connectHOC(selectorFactory, {
// 要领称号,用在毛病提醒信息
methodName: 'connect',
// 终究衬着的组件称号
getDisplayName: name => `Connect(${name})`,
shouldHandleStateChanges: Boolean(mapStateToProps),
// 以下是通报给 selectFactory
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
// any extra options args can override defaults of connect or connectAdvanced
...extraOptions
})
}
}
这里实行了connectHOC()
,通报了上面已讲过的参数,而connectHOC = connectAdvanced
因而我们进入末了一个对外API
,connectAdvanced
connectAdvanced
connectAdvanced
函数,之前也提过,就是一个实行、组件衬着和组件更新的处所。
它内里没有什么新概念,都是将我们上面讲到的参数举行挪用,末了依据效果举行衬着新组件。
照样从源码最先
export default function connectAdvanced(
selectorFactory,
{
// 实行后作用于connect这个HOC组件称号
getDisplayName = name => `ConnectAdvanced(${name})`,
// 用于毛病提醒
methodName = 'connectAdvanced',
// 有REMOVED标志,这里不关注
renderCountProp = undefined,
// 肯定connect这个HOC是不是定阅state更改,彷佛已没有用到了
shouldHandleStateChanges = true,
// 有REMOVED标志,这里不关注
storeKey = 'store',
// 有REMOVED标志,这里不关注
withRef = false,
// 是不是经由过程 forwardRef 暴露出传入的Component的DOM
forwardRef = false,
// React的createContext
context = ReactReduxContext,
// 其他的(比较要领,参数处置惩罚要领等)将会通报给上面的 selectFactory
...connectOptions
} = {}
) {
/* ... */
}
参数也没什么迥殊的,有一个forwardRef
作用就是能猎取到我们传入的Component
的DOM。
这里也不深切。
接着看
export default function connectAdvanced(
/* 上面已讲 */
) {
/* ...对参数的一些考证和提醒哪些参数已取消... */
// 定义Context
const Context = context
return function wrapWithConnect(WrappedComponent) {
/* ...搜检 WrappedComponent 是不是符合要求... */
/* ...猎取传入的WrappedComponent的称号... */
/* ...经由过程WrappedComponent的称号计算出当前HOC的称号... */
/* ...猎取一些上面的参数(没有新的参数,都是之前见过的)... */
// Component就是React.Component
let OuterBaseComponent = Component
let FinalWrappedComponent = WrappedComponent
// 是不是纯组件
if (pure) {
OuterBaseComponent = PureComponent
}
/* 定义 makeDerivedPropsSelector 要领,作用背面讲 */
/* 定义 makeChildElementSelector 要领,作用背面讲 */
/* 定义 Connect 组件,作用背面讲 */
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
/* ...假如是forWardRef 为true的状况,此处不深切... */
// 静态要领转换
return hoistStatics(Connect, WrappedComponent)
}
}
这一段迥殊长,因而我将不太主要的直接用解释申清楚明了它们在做什么,详细代码就不放了(不主要)。
而且定义了3个新东西,makeDerivedPropsSelector
,makeChildElementSelector
,Connect
。
先看末了一句hoistStatics
就是hoist-non-react-statics
,它的作用是将组件WrappedComponent
的一切非React
静态要领通报到Connect
内部。
那末终究它照样返回了一个Connect
组件。
Connect组件
这个组件已是我们写了完全connect(...)(Component)
的返回值了,所以能肯定,只需挪用<Connect />
,就可以衬着出一个新的组件出来。
因而它的功用就是肯定是不是反复更新组件和肯定究竟更新什么?
看一个组件,从constructor
看起
class Connect extends OuterBaseComponent {
constructor(props) {
super(props)
/* ...提醒一些无用的参数...*/
this.selectDerivedProps = makeDerivedPropsSelector()
this.selectChildElement = makeChildElementSelector()
this.renderWrappedComponent = this.renderWrappedComponent.bind(this)
}
/* ... */
}
绑定了一个要领,看名字是render的意义,先不论它。
实行了2个函数。
Connect
组件还没完,这里先放着,我们先看makeDerivedPropsSelector
和makeChildElementSelector
makeDerivedPropsSelector
function makeDerivedPropsSelector() {
// 闭包贮存上一次的实行效果
let lastProps
let lastState
let lastDerivedProps
let lastStore
let sourceSelector
return function selectDerivedProps(state, props, store) {
// props和state都和之前相称 直接返回上一次的效果
if (pure && lastProps === props && lastState === state) {
return lastDerivedProps
}
// 当前store和lastStore不等,更新lastStore
if (store !== lastStore) {
lastStore = store
// 终究挪用 selectorFactory 了
sourceSelector = selectorFactory(
store.dispatch,
selectorFactoryOptions
)
}
// 更新数据
lastProps = props
lastState = state
// 返回的就是终究的包括一切响应的 state 和 props 的效果
const nextProps = sourceSelector(state, props)
// 终究的比较
if (lastDerivedProps === nextProps) {
return lastDerivedProps
}
lastDerivedProps = nextProps
return lastDerivedProps
}
}
也许的说,makeDerivedPropsSelector
的实行,先推断了当前传入的props(组件的props)
和state(redux传入的state)
跟之前的是不是全等,假如全等就不需要更新了;
假如不等,则挪用了高阶函数selectFactory
,而且取得终究数据,末了再推断终究数据和之前的终究数据是不是全等。
为何第一次推断了,还要推断第二次,而且都是===
推断?
由于第一次猎取的state
是redux
传入的,是全部APP的一切数据,它们不等申明有组件更新了,但不肯定是不是是当前组件;
第二次比较的是当前组件的最新数据和之前数据对照。
如今,我们晓得selectFactory
的作用是猎取当前组件的的最新数据,深切源码看看。
selectFactory
export default function finalPropsSelectorFactory(
// redux store的store.dispatch
dispatch,
// 3种已肯定了的处置惩罚要领
{ initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
// 返回一个针对用户传入的范例的剖析函数
// 比方 mapStateToProps 假如是function,那末就返回proxy,proxy可以推断是不是需要ownProps,而且对高阶函数的 mapStateToProps 举行2次处置惩罚,
// 终究确保返回一个plainObject,不然报错
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
// 默许pure题目true,因而实行 pureFinalPropsSelectorFactory(...)
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
参数就不说了,看解释。
以下3个,究竟返回了什么,源码在wrapMapToProps.js
,上面也说过这个文件内部做了什么事变。
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
这3个挪用返回的一个函数,名字叫proxy
,这个proxy
一旦挪用,
就可以返回经由mapStateToProps
, mapDispatchToProps
, mergeProps
这3个参数处置惩罚事后的数据(plainObject
)。
接下来:
const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
// 默许pure题目true,因而实行 pureFinalPropsSelectorFactory(...)
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
返回了selectorFactory
的挪用值,也就是pureFinalPropsSelectorFactory
(pure默许为true)。
看pureFinalPropsSelectorFactory
,它的代码不少,但逻辑很清楚明了,大方向就是对照数据。
这里症结的怎样比较不列代码,只用解释讲邃晓它的逻辑。
export function pureFinalPropsSelectorFactory(
// 接收3个proxy要领
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
// 接收3个比较要领
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
/* ...定义变量保留之前的数据(闭包)... */
function handleFirstCall(firstState, firstOwnProps) {
/* ...定义第一次实行数据比较的要领,也就是简朴的赋值给上面定义的闭包变量... */
}
function handleNewPropsAndNewState() {
/* 当state和props都有更改时的处置惩罚要领 */
}
function handleNewProps() {
/* 当state无更改,props有更改时的处置惩罚要领 */
}
function handleNewState() {
/* 当state有更改,props无更改时的处置惩罚要领 */
}
// 后续数据比较的要领
function handleSubsequentCalls(nextState, nextOwnProps) {
// 浅比较
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
// 全等比较
const stateChanged = !areStatesEqual(nextState, state)
// 更新数据
state = nextState
ownProps = nextOwnProps
// 当发作不相称的3种状况(症结)
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)
}
}
上面的闭包变量贮存了上一次的数据,症结点就是当和这一次的数据比较后,假如处置惩罚更新。
react-redux
将它分为3种状况
-
state
和props
都相称。 -
state
相称,props
不等。 -
state
不等,props
相称。
第一种:
state
和props
都相称- mapStateToProps(proxy):
不论是不是定阅
ownProps
,实行mapStateToProps
, 由于state
有更改。 - mapDispatchToProps(proxy):
只要定阅了
ownProps
,才会实行mapDispatchToProps
,由于state
更改与mapDispatchToProps
无影响。 - mergedProps(proxy):
一定实行,将一切效果兼并。
- mapStateToProps(proxy):
第二种:
state
相称,props
不等- mapStateToProps(proxy):
只要定阅了
ownProps
,才会实行mapStateToProps
, 由于state
无更改。 - mapDispatchToProps(proxy):
只要定阅了
ownProps
,才会实行mapDispatchToProps
,由于state
更改与mapDispatchToProps
无影响。 - mergedProps(proxy):
一定实行,将一切效果兼并。
- mapStateToProps(proxy):
第三种:
state
不等,props
相称- mapStateToProps(proxy):
不论是不是定阅
ownProps
,实行mapStateToProps
, 由于state
有更改。注重,这里效果需要
浅比较
推断由于假如没有
浅比较
搜检,而二者恰好浅比较相称
,
那末末了也会以为返回一个新的props,也就是相当于反复更新了。之所以第一个
state
和props
都有更改的不需要浅比较搜检,
是由于假如props
变了,则必需要更新组件。 - mapDispatchToProps(proxy):
不会实行,由于它只关注
props
。 - mergedProps(proxy):
只要上面浅比较不等,才会实行。
- mapStateToProps(proxy):
makeDerivedPropsSelector
的总结:
经由过程闭包治理数据,而且经由过程浅比较和全等比较推断是不是需要更新组件数据。
makeChildElementSelector
makeChildElementSelector
也是一个高阶函数,贮存了之前的数据
和组件
,而且推断与当前的推断。
这里是终究衬着组件的处所,由于需要推断一下适才终究给出的数据是不是需要去更新组件。
2个逻辑:
- 数据与之前不等(
===
),更新组件。 -
forWardRef
属性值与之前不等,更新组件。
不然,返回旧组件(不更新)。
继承回到Connect
组件。
以后就是render
了
render() {
// React的createContext
const ContextToUse = this.props.context || Context
return (
<ContextToUse.Consumer>
{this.renderWrappedComponent}
</ContextToUse.Consumer>
)
}
Context.Consumer
内部必需是一个函数,这个函数的参数就是Context.Provider
的value
,也就是redux
的store
。
renderWrappedComponent
末了一个函数:renderWrappedComponent
renderWrappedComponent(value) {
/* ...考证参数有效性... */
// 这里 storeState=store.getState()
const { storeState, store } = value
// 传入自定义组件的props
let wrapperProps = this.props
let forwardedRef
if (forwardRef) {
wrapperProps = this.props.wrapperProps
forwardedRef = this.props.forwardedRef
}
// 上面已讲了,返回终究数据
let derivedProps = this.selectDerivedProps(
storeState,
wrapperProps,
store
)
// 返回终究衬着的自定义组件
return this.selectChildElement(derivedProps, forwardedRef)
}
总算完毕了,可以有点杂沓,做个总结吧。
总结
我把react-redux
的实行流程分为3个阶段,离别对应我们的代码编写(搭配导图浏览)
一张导图:
第一阶段:
对应的用户代码:
<Provider store={store}>
<App />
</Provider>
实行内容有:
- 定义了
Provider
组件,这个组件内部定阅了redux
的store
,保证当store
发作更改,会马上实行更新。
第二阶段:
对应的用户代码:
connect(mapStateToProps,mapDispatchToProps,mergeProps,options)
实行内容有:
-
connect
接收了参数(mapStateToProps
,mapDispatchToProps
,mergeProps
,options
)。 -
connect
接收了对参数怎样处置惩罚计划(3个...Factories
)。 -
connect
接收了参数的效果比较计划(selectFactory
) - 定义了比较计划(4个
are...Equal
,实在就是全等比较
和浅比较
)。
第三阶段:
对应的用户代码:
let newComponent=connect(...)(Component)
<newComponent />
实行内容有:
- 接收自定义组件(
Component
)。 - 建立一个
Connect
组件。 - 将
Component
的非React
静态要领转移到Connect
。 - 猎取
Provider
传入的数据
(redux
的全部数据),应用闭包保留数据,用于和将来数据做比较。 - 当比较(
===
)有更改,实行上一阶段传入的参数,猎取当前组件真正的数据。 - 应用闭包保留当前组件真正的数据,用于和将来作比较。
- 经由过程全等和浅比较,处置惩罚
state
更改和props
更改的逻辑,推断返回新数据照样旧数据。 - 应用闭包保留衬着的组件,经由过程上面返回的终究数据,推断需要返回新组件照样就组件。
逻辑理顺了,照样很好明白的。
个中第三阶段就是对外APIconnectAdvanced
的实行内容。
此处检察更多前端源码浏览内容。
也许哪一天,我们需要设想一个专用的数据治理系统,那末就应用好connectAdvanced
,
我们要做的就是编写一个自定义第二阶段
的逻辑系统。
谢谢浏览!