摘要: 本文围绕一个简单的例子展开,主要聚焦于react-redux架构下数据的流动,包括数据的派发和更新。首先介绍传统模式,然后介绍一下hooks模式的更新 本文基于react-redux7.2.4

传统模式

一个典型的react-redux应用一般具有如下结构:

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import { connect,Provider } from "react-redux"
  4. import { createStore } from "redux"
  5. const initialState = { value: 0 }
  6. const ADD_ACTION = 'add';
  7. const reducer = (state = initialState ,action ) => {
  8. switch(action.type){
  9. case ADD_ACTION:
  10. return {
  11. value: ++state.value
  12. }
  13. default:
  14. break;
  15. }
  16. return state
  17. }
  18. const store = createStore(reducer);
  19. const mapStateToProps = state => {
  20. return{
  21. value: state.value
  22. }
  23. }
  24. const mapDispatchToProps = dispatch => {
  25. return {
  26. update(payload){
  27. dispatch({type: ADD_ACTION,payload})
  28. }
  29. }
  30. }
  31. const App = connect(mapStateToProps,mapDispatchToProps)( function InnerCommponent({value,update}){
  32. return(
  33. <div>
  34. value的值是: {value}
  35. <div>
  36. <button onClick={ update }>点击更新</button>
  37. </div>
  38. </div>
  39. )
  40. })
  41. const rootElement = document.getElementById('root')
  42. ReactDOM.render(
  43. <Provider store={store}>
  44. <App />
  45. </Provider>,
  46. rootElement
  47. )

基于此代码 我们从上到下 依次梳理梳理

篇1 store究竟是个什么东西

storecreateStore(reducer)创建而来,最后的store具有如下的数据结构:

  1. {
  2. dispatch,
  3. subscribe,
  4. getState,
  5. replaceReducer,
  6. [$$observable]: observable,
  7. }

其中 subscribe用于完成订阅,dispatch就是用于发起一次state更新的函数,并唤起subscribe订阅的回调,getState返回当前的state,replaceReducer用于替换reducer.
完成订阅的步骤非常简单,就是往一个数组里添加回调函数:

  1. ...
  2. nextListeners.push(listener)
  3. ...

而唤起的时候,就是遍历listeners,依次执行回调:

  1. ...
  2. const listeners = (currentListeners = nextListeners)
  3. for (let i = 0; i < listeners.length; i++) {
  4. const listener = listeners[i]
  5. listener()
  6. }
  7. ...

篇2 connect究竟是个什么东西,以及他的作用

connect 主要有以下几个作用:

  1. 初始化mapStateToProps,mapDispatchToProps,以及mergeProps(这是connect的第三个参数),主要是参数校验,然后会将其包装成具有统一签名:initProxySelector(dispatch, { displayName })的函数。
  2. 初始化selectorFactory,该函数用于生成最终我们定义的组件的props。
  3. 更多的工作,其实就是为connectAdvanced函数提供初始入参配置。

最终会返回connectAdvanced函数的执行结果。

connectAdvanced除了基本的一些初始化参数配置外,还有一点就是拿到了context,该context的value在Provider构建时进行初始化,会传递storesubscription两个参数。store前边已经介绍过了,现在对subscription做一简单介绍:

subscriptionSubscription类的实例,主要是封装了组件到store的订阅逻辑,同时也可以处理嵌套的子组件的订阅逻辑,保证更新是由父到子进行的,一般调用该实例的trySubscribe方法完成订阅。

说完subscription,我们继续connectAdvancedconnectAdvanced最终会返回wrapWithConnect函数,上述代码中的App也正是wrapWithConnect的执行结果。

wrapWithConnect,可以看成一个高阶组件,接受WrappedComponent即一个React组件WrappedComponent作为入参,在其内部主要干了这么几件事:

  • 初始化createChildSelector,该函数用于计算出当前组件的props
  • 初始化ConnectFunction, 该内部主要功能有:

    • 使用useMemo完成Subscription的初始化,此Subscription实例初始化的时候,作为子组件的subscription,一般都会传进去父组件的subscription。父组件的subscription就是由connectAdvanced拿到的context进行传递的。
    • useLayoutEffect(客户端)或useEffect(ssr),完成订阅(源代码做了简单的修改):

      1. ...
      2. subscription.onStateChange = checkForUpdates
      3. subscription.trySubscribe()
      4. ...
      5. ...
      6. //subscription内部 trySubscribe
      7. // handleChangeWrapper 最终执行时会调起onStateChange
      8. this.parentSub.addNestedSub(this.handleChangeWrapper)
      9. ...
      10. // addNestedSub
      11. ...
      12. this.listeners.subscribe(listener)
      13. ...

      可以看到,子组件内部的订阅最终会挂在父组件传下来的subscriptionlisteners上。这样就完成了订阅。

      subscriptionlisteners,是subscription完成其订阅的内部数据结构,使用双向链表即:

      1. {
      2. callback,
      3. next: null,
      4. prev: null,
      5. }

      保存订阅的回调函数,唤起的时候,代码如下:

      1. ...
      2. batch(() => {
      3. let listener = first
      4. while (listener) {
      5. listener.callback()
      6. listener = listener.next
      7. }
      8. })
      9. ...

      就是一个简单的链表遍历,值得一提的是这个batch,实际运行后,调用的是React内部的名为batchedUpdates$1的函数,该函数会改变executionContext的值(executionContext |= BatchedContext),其直接结果就是在React更新时,scheduleUpdateOnFiber的执行不会走renderRootSync这样的同步更新,而是会安排一个异步回调,将所有更新合并进行。这一步可以称之为性能优化

      • 计算出actualChildProps后 ,最终返回 :
        1. <ContextToUse.Provider value={overriddenContextValue}>
        2. {renderedWrappedComponent}
        3. </ContextToUse.Provider>
        ContextToUse一般就是上边提到的contextoverriddenContextValue数据结构同context的value,只是会将subscription替换为当前组件的subscription。在组件上包裹一层provider是因为,react总是就近取context,这样一来可以保证renderedWrappedComponent的嵌套子组件如果访问ContextToUse总是可以取到和当前组件相同的context实例,并且当子组件有订阅行为时,可以将其订阅在自己的addNestedSub,保证更新由父到子进行。
  • 最后将WrappedComponent的静态属性合并到ConnectFunction上,返回ConnectFunction

篇3 Provider

实际就是一个普通的组件,主要办了这么几件事:

  • 初始化自身的subscription,
    1. const subscription = new Subscription(store)
    并协同store使用useMemo一起挂到contextValue
  • useLayoutEffect(客户端)或useEffect(ssr),完成订阅(源代码做了简单的修改):
    1. ...
    2. subscription.onStateChange = subscription.notifyNestedSubs
    3. ...
    4. subscription.trySubscribe()
    5. ...
    6. //subscription内部 trySubscribe
    7. ...
    8. this.store.subscribe(this.handleChangeWrapper)
    9. ...
    10. //store.subscribe nextListeners介绍store的时候提到过就是一个数组,listener就是上边传过去的回调
    11. nextListeners.push(listener)
    12. ...
  • 最后返回
    1. <Context.Provider value={contextValue}>{children}</Context.Provider>
    ConnectFunctionProvider是在React更新流程的beiginWor阶段调用的。

介绍完基本概念可以正式开始介绍数据的更新和派发了。

篇4 数据派发

派发数据相对来说比较简单,可以想想,当数据更新后,在一个使用connect一顿操作过后的原始组件,其对外窗口只有一个,那就是props,所以更新后的数据主要就是props的计算。

ConnectFunction内部计算props时,自childPropsSelector(store.getState(), wrapperProps)始,中间经历了很长的链路,这里的细节我们无需关注,但是可以给出最后的结果就是:

  1. // pureFinalPropsSelectorFactory
  2. ...
  3. stateProps = mapStateToProps(state, ownProps)
  4. dispatchProps = mapDispatchToProps(dispatch, ownProps)
  5. mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
  6. ...

最后的返回结果便是,stateProps, dispatchProps, ownProps合并之后的结果。基于此呢,组件就可以从props读取到react-redux派发下来的数据了。

篇5 数据更新

一次更新由dispatch发起,基本工作就是更新state,唤起订阅的回调函数

流程概览

react-redux-dispatch.png

更多相关文章

  1. Vue 中的 .sync 修饰符stop修饰符prevent修饰符可以press.enter
  2. 几种常用设计模式的简单示例
  3. 快递100显示查询错误?快递100快递查询类FAQ
  4. bootstrap常用组件样式使用之,导航,列表,按钮
  5. Vue组件及路由
  6. vuex面试题
  7. vue父组件与子组件的数据传递
  8. PHP:【微信小程序】微信小程序部分组件,微信小程序轮播图
  9. 微信小程序数据操作、自定义事件、微信API、路由组件、变量作用

随机推荐

  1. AES加密解密Android版
  2. android 自定义输入框
  3. ANDROID轻量级JSON序列化和反序列化[转]
  4. android技术牛人的博客
  5. android 按钮Button单击背景切换
  6. android 特效资源
  7. android @+id 含义
  8. Android ViewPager动画第三方库(MagicView
  9. Android分区详解:boot, system, recovery,
  10. android pdf 类库