乐趣区

关于javascript:神奇的reactredux

一、引言

之前学习了 Redux,而后发现有 Redux 的中央简直都少不了 react-redux 这个库,它能够说是 建设了 React 组件和 Redux 之间的桥梁。所以我顺便学习了 react-redux,觉得很有必要记录一下。

二、为什么须要应用 react-redux

如果不必 react-redux 的话,咱们想在 React 组件中应用 Redux,就不得不引入 store,应用 store.dispatch(action)去分派 action,间接地扭转状态。乍一看,这样没什么问题啊,是的,如果只是一个组件的话这样的确没什么问题。然而,试想,如果该组件的子组件也想用名为 xxx 的 state 的话,岂不是又得引入 store,而后又是像父组件那样一套流程下来,这样的确失效,然而显得代码十分的臃肿,可维护性很差,因为做了很多反复的工作。然而如果咱们引入一个容器组件,这个组件能够应用 props 的形式将 state 和 dispatch 传递给子组件,这样就省去了很多反复的工作。这时,react-redux 库就发挥作用了,建设起 React 和 store 的桥梁,能够将 store 和 dispatch 映射给 props,这样 React 的组件就能够通过 props 将 state 和 dispatch 向下传递;或者能够应用 Provider 提供一个 context,将 store 无限度向下传递给子孙组件。

三、react-redux 做了什么

先说一下 react-redux 做了什么:

  • 提供 Subscrption 类,实现 订阅更新的逻辑
  • 提供 Provider,将 store 传入 Provider,便于上层组件从 context 或者 props 中获取 store;并订阅 store 的变动,便于在 store 变动的时候执行被订阅到 react-redux 内的更新函数
  • 提供 selector,负责 将获取 store 中的 state 和 dispacth 一些 action 的函数(或者间接就是 dispatch)或者组件本人的 props,从中抉择出组件须要的值,作为 selector 的返回值
  • 提供connect 高阶组件,次要做了两件事:

    • 执行 selector,获取到要注入到组件中的值,将它们注入到组件的 props
    • 订阅 props 的变动,负责在 props 变动的时候更新组件

四、react-redux 是怎么做到的

当我理解到 react-redux 的大抵性能之后,我脑海里立马产生了三个疑难,别离是:

  • Provider 是如何将 store 放入 context 中的?
  • 如何将 store 中的 state 和 dispatch(或调用 dispatch 的函数)注入组件中的 props 的?
  • React-Redux 如何做到当 store 变动,被 connect 的组件也会更新的?(如何监测 store 的变动?)

五、解决以上提出的问题

1.Provider 是怎么把 store 放入 context 中的?

先来看 Provider.js 的源码:

function Provider({store, context, children}) {const contextValue = useMemo(() => {
    // 申明一个 Subscription 实例。订阅,监听 state 变动来执行 listener,都由实例来实现
    const subscription = new Subscription(store)
    // 绑定监听,当 state 变动时,告诉订阅者更新页面,实际上也就是在 connect 过程中被订阅到 react-redux 的 subscription 对象上的更新函数
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  // 获取以后的 store 的 state,作为上一次的 state,将会在组件挂载结束后,// 与 store 新的 state 比拟,不统一的话更新 Provider 组件
  const previousState = useMemo(() => store.getState(), [store])

  useEffect(() => {
    // 这会在组件渲染之后执行, 所以这个时候 contextValue 曾经返回
    // 在组件挂载结束后,订阅更新。const {subscription} = contextValue       //contextValue = {store, subscription}
    // 这里先了解为最开始的时候须要订阅更新函数,便于在状态变动的时候执行更新函数,相当于是注册了一个监听,在监听 state 的变动。subscription.trySubscribe()

    // 如果前后的 store 中的 state 有变动,那么就去更新 Provider 组件
    if (previousState !== store.getState()) {subscription.notifyNestedSubs()
    }
    return () => {subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

if (process.env.NODE_ENV !== 'production') {
  //propTypes 仅在开发模式下进行查看
  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

所以联合代码看这个问题:Provider 是怎么把 store 放入 context 中的 ,很好了解。
Provider 最次要的性能是 从 props 中获取咱们传入的 store,并将 store 作为 context 的其中一个值,向上层组件下发

然而,一旦 store 变动,Provider 要有所反馈,以此保障将始终将最新的 store 放入 context 中。所以这里要 用订阅来实现更新 。天然引出 Subscription 类, 通过该类的实例,将 onStateChange 监听到一个可更新 UI 的事件 this.notifySubscribers 上:

subscription.onStateChange = this.notifySubscribers

组件挂载实现后,去订阅更新 ,至于这里订阅的是什么,要看 Subscription 的实现。这里先给出论断: 实质上订阅的是 onStateChange,实现订阅的函数是:Subscription 类之内的 trySubscribe。

this.state.subscription.trySubscribe()

再接着,如果前后的 state 不一样,那么就去告诉订阅者更新,onStateChange 就会执行,Provider 组件就会执行上层组件订阅到 react-redux 的更新函数。当 Provider 更新实现(componentDidUpdate), 会去比拟一下前后的 store 是否雷同,如果不同,那么用新的 store 作为 context 的值,并且勾销订阅,从新订阅一个新的 Subscription 实例。保障用的数据都是最新的。

// 如果前后的 store 中的 state 有变动,那么就去更新 Provider 组件
if (previousState !== store.getState()) {subscription.notifyNestedSubs()
}
return () => {subscription.tryUnsubscribe()
  subscription.onStateChange = null
}

// 未实现 …

退出移动版