[源码浏览]高性能和可扩大的React-Redux

注重:文章很长,只想相识逻辑而不深切的,可以直接跳到
总结部份

初识

起首,从它暴露对外的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是什么呢,就是对reduxstore实行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段的意义就是,每当数据变了,就取消上一次数据的定阅,在定阅本次的数据,
当要烧毁组件,取消定阅。

一段题外话(可跳过):

这个逻辑用HooksuseEffect几乎圆满婚配!

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这个文件内部做了以下事变:

  1. 定义了一个处置惩罚object的要领(简朴的返回即可,由于终究目标就是要object)。
  2. 定义了一个处置惩罚函数高阶函数(实行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个变量定义明确了,都是对应的参数的适宜的处置惩罚要领。

至此,我们已完成了第二阶段,

做个小总结,第二阶段做了哪些事:

  1. connect接收了对参数处置惩罚计划(3个...Factories)。
  2. connect接收了参数的效果比较计划(selectFactory)
  3. connect接收了参数(mapStateToProps,mapDispatchToProps,mergeProps,options)。
  4. 定义了比较计划(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

因而我们进入末了一个对外APIconnectAdvanced

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个新东西,makeDerivedPropsSelectormakeChildElementSelector,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组件还没完,这里先放着,我们先看makeDerivedPropsSelectormakeChildElementSelector

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,而且取得终究数据,末了再推断终究数据和之前的终究数据是不是全等。

为何第一次推断了,还要推断第二次,而且都是===推断?

由于第一次猎取的stateredux传入的,是全部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种状况

  • stateprops都相称。
  • state相称,props不等。
  • state不等,props相称。
  • 第一种:stateprops都相称

    • mapStateToProps(proxy):

      不论是不是定阅ownProps,实行mapStateToProps, 由于state有更改。

    • mapDispatchToProps(proxy):

      只要定阅了ownProps,才会实行mapDispatchToProps,由于state更改与mapDispatchToProps无影响。

    • mergedProps(proxy):

      一定实行,将一切效果兼并。

  • 第二种:state相称,props不等

    • mapStateToProps(proxy):

      只要定阅了ownProps,才会实行mapStateToProps, 由于state无更改。

    • mapDispatchToProps(proxy):

      只要定阅了ownProps,才会实行mapDispatchToProps,由于state更改与mapDispatchToProps无影响。

    • mergedProps(proxy):

      一定实行,将一切效果兼并。

  • 第三种:state不等,props相称

    • mapStateToProps(proxy):

      不论是不是定阅ownProps,实行mapStateToProps, 由于state有更改。

      注重,这里效果需要浅比较推断

      由于假如没有浅比较搜检,而二者恰好浅比较相称
      那末末了也会以为返回一个新的props,也就是相当于反复更新了。

      之所以第一个stateprops都有更改的不需要浅比较搜检,
      是由于假如props变了,则必需要更新组件。

    • mapDispatchToProps(proxy):

      不会实行,由于它只关注props

    • mergedProps(proxy):

      只要上面浅比较不等,才会实行。

makeDerivedPropsSelector的总结:

经由过程闭包治理数据,而且经由过程浅比较和全等比较推断是不是需要更新组件数据。

makeChildElementSelector

makeChildElementSelector也是一个高阶函数,贮存了之前的数据组件,而且推断与当前的推断。

这里是终究衬着组件的处所,由于需要推断一下适才终究给出的数据是不是需要去更新组件。

2个逻辑:

  1. 数据与之前不等(===),更新组件。
  2. forWardRef属性值与之前不等,更新组件。

不然,返回旧组件(不更新)。

继承回到Connect组件。

以后就是render

render() {
  // React的createContext
  const ContextToUse = this.props.context || Context

  return (
    <ContextToUse.Consumer>
      {this.renderWrappedComponent}
    </ContextToUse.Consumer>
  )
}

Context.Consumer内部必需是一个函数,这个函数的参数就是Context.Providervalue,也就是reduxstore

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个阶段,离别对应我们的代码编写(搭配导图浏览)

一张导图:

《[源码浏览]高性能和可扩大的React-Redux》

第一阶段:

对应的用户代码:

<Provider store={store}>
  <App />
</Provider>

实行内容有:

  1. 定义了Provider组件,这个组件内部定阅了reduxstore,保证当store发作更改,会马上实行更新。

第二阶段:

对应的用户代码:

connect(mapStateToProps,mapDispatchToProps,mergeProps,options)

实行内容有:

  1. connect接收了参数(mapStateToProps,mapDispatchToProps,mergeProps,options)。
  2. connect接收了对参数怎样处置惩罚计划(3个...Factories)。
  3. connect接收了参数的效果比较计划(selectFactory)
  4. 定义了比较计划(4个are...Equal,实在就是全等比较浅比较)。

第三阶段:

对应的用户代码:

let newComponent=connect(...)(Component)

<newComponent />

实行内容有:

  1. 接收自定义组件(Component)。
  2. 建立一个Connect组件。
  3. Component的非React静态要领转移到Connect
  4. 猎取Provider传入的数据(redux的全部数据),应用闭包保留数据,用于和将来数据做比较。
  5. 当比较(===)有更改,实行上一阶段传入的参数,猎取当前组件真正的数据。
  6. 应用闭包保留当前组件真正的数据,用于和将来作比较。
  7. 经由过程全等和浅比较,处置惩罚state更改和props更改的逻辑,推断返回新数据照样旧数据。
  8. 应用闭包保留衬着的组件,经由过程上面返回的终究数据,推断需要返回新组件照样就组件。

逻辑理顺了,照样很好明白的。

个中第三阶段就是对外APIconnectAdvanced的实行内容。

此处检察更多前端源码浏览内容。

也许哪一天,我们需要设想一个专用的数据治理系统,那末就应用好connectAdvanced
我们要做的就是编写一个自定义第二阶段的逻辑系统。

谢谢浏览!

    原文作者:stonehank
    原文地址: https://segmentfault.com/a/1190000017062631
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞