来自 Web前端 2020-05-07 05:42 的文章
当前位置: 网上澳门金莎娱乐 > Web前端 > 正文

带着问题看React-Redux源码

React-Redux做了什么

前面写了两篇文章《React组件性能优化》《Redux性能优化》,分别针对React和Redux在使用上的性能优化给了一些建议。但是React和Redux一起使用还需要一个工具React-Redux,这一篇就说一下React-Redux在使用上的一些性能优化建议。

文章用了一周多的时间写完,粗看了一遍源码之后,又边看边写。源码不算少,我尽量把结构按照最容易理解的方式梳理,努力按照浅显的方式将原理讲出来,但架不住代码结构的复杂,很多地方依然需要花时间思考,捋清函数之间的调用关系并结合用法才能明白。文章有点长,能看到最后的都是真爱

Provider

一个很简单的React组件,它主要的作用是把store放到context中,connect就可以获取store,使用store的方法,比如dispatch。其实没有被connect的组件通过声明contextTypes属性也是可以获取store,使用store的方法的,但是这个时候,如果使用dispatch修改了store的state,React-Redux并不能把修改后的state作为props给React组件,可能会导致UI和数据不同步,所以这个时候一定要清楚自己在做什么。

const mapDispatchToProps = { increaseAction() { return dispatch = dispatch({ type: INCREASE }) }, decreaseAction() { return dispatch = dispatch({ type: DECREASE }) } }

connect

一个柯里化函数,函数将被调用两次。第一次是设置参数,第二次是组件与 Redux store 连接。connect 函数不会修改传入的 React 组件,返回的是一个新的已与 Redux store 连接的组件,而且你应该使用这个新组件。connect的使用方式是connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component),第一次调用的时候4个参数都是可选。

  1. mapStateToProps在store发生改变的时候才会调用,然后把返回的结果作为组件的props。
  2. mapDispatchToProps主要作用是弱化Redux在React组件中存在感,让在组件内部改变store的操作感觉就像是调用一个通过props传递进来的函数一样。一般会配合Redux的bindActionCreators使用。如果不指定这个函数,dispatch会注入到你的组件props中。
  3. mergeProps用来指定mapStateToProps、mapDispatchToProps、ownProps(组件自身属性)的合并规则,合并的结果作为组件的props。如果要指定这个函数,建议不要太复杂。
  4. options里面主要关注pure,如果你的组件仅依赖props和Redux的state,pure一定要为true,这样能够避免不必要的更新。
  5. Component就是要被连接的React组件,组件可以是任意的,不一定是AppRoot。一般会是需要更新store、或者是依赖store中state的最小组件。因为被连接的组件在Redux的state改变后会更新,大范围的更新对性能不友好,而且其中有些组件可能是没必要更新也会更新,所以要尽量拆分、细化,connect仅仅要更新store或依赖store的state的最小组件。
const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

参考

React-Redux

Reselect

现在我们应该可以明白,这三个问题对应着React-Redux的三个核心概念:

Reselect

mapStateToProps也被叫做selector,在store发生变化的时候就会被调用,而不管是不是selector关心的数据发生改变它都会被调用,所以如果selector计算量非常大,每次更新都重新计算可能会带来性能问题。Reselect能帮你省去这些没必要的重新计算。
Reselect 提供 createSelector 函数来创建可记忆的 selector。createSelector 接收一个 input-selectors 数组和一个转换函数作为参数。如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。这样就可以避免不必要的计算,为性能带来提升。

生成的过程是在connect的核心函数connectAdvanced中,这个时候可以拿到当前context中的store,进而用store传入selectorFactory生成selector,其形式为

总结

谨慎使用context中的store

被connect组件更新的时候影响范围尽量小,避免不必要更新

使用Resselect避免不必要的selector计算

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

React-Redux是官方的React和Redux链接工具

我们先给出结论,说明React-Redux做了什么工作:

网上澳门金莎娱乐,大功告成

这一步在connectAdvanced函数内,创建一个调用selectorFactory,将store以及初始化后的mapToProps函数和其他配置传进去。selectorFactory内执行mapToProps(也就是selector),获取返回值,最后将这些值传入组件。

说到这里,就要引出一个概念:selector。最终注入到组件的props是selectorFactory函数生成的selector的返回值,所以也就是说,mapStateToProps和mapDispatchToProps本质上就是selector。

原文:

先从Provider组件入手,代码不多,直接上源码

更新谁?订阅的更新函数是什么?如何判断状态变化?

export default function connectAdvanced( selectorFactory, { getDisplayName = name = `ConnectAdvanced(${name})`, methodName = 'connectAdvanced', renderCountProp = undefined, shouldHandleStateChanges = true, storeKey = 'store', withRef = false, forwardRef = false, context = ReactReduxContext, ...connectOptions } = {}) { const Context = context return function wrapWithConnect(WrappedComponent) { // ...忽略了其他代码 // selectorFactoryOptions是包含了我们初始化的mapToProps的一系列参数 const selectorFactoryOptions = { ...connectOptions, getDisplayName, methodName, renderCountProp, shouldHandleStateChanges, storeKey, displayName, wrappedComponentName, WrappedComponent } // pure表示只有当state或者ownProps变动的时候,重新计算生成selector。 const { pure } = connectOptions /* createChildSelector 的调用形式:createChildSelector(store)(state, ownProps), createChildSelector返回了selectorFactory的调用,而selectorFactory实际上是其内部根据options.pure返回的 impureFinalPropsSelectorFactory 或者是 pureFinalPropsSelectorFactory的调用,而这两个函数需要的参数是 mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options 除了dispatch,其余参数都可从selectorFactoryOptions中获得。调用的返回值,就是selector。而selector需要的参数是 (state, ownprops)。所以得出结论,createChildSelector(store)就是selector */ function createChildSelector(store) { // 这里是selectorFactory.js中finalPropsSelectorFactory的调用(本质上也就是上面我们初始化的mapToProps的调用),传入dispatch,和options return selectorFactory(store.dispatch, selectorFactoryOptions) } function ConnectFunction(props) { const store = props.store || contextValue.store // 仅当store变化的时候,创建selector // 调用childPropsSelector = childPropsSelector(dispatch, options) const childPropsSelector = useMemo(() = { // 每当store变化的时候重新创建这个选择器 return createChildSelector(store) }, [store]) // actualChildProps就是最终要注入到组件中的props,也就是selector的返回值。 const actualChildProps = usePureOnlyMemo(() = { return childPropsSelector(store.getState(), wrapperProps) }, [store, previousStateUpdateResult, wrapperProps]) const renderedWrappedComponent = useMemo( // 这里是将props注入到组件的地方 () = WrappedComponent {...actualChildProps} /, [forwardedRef, WrappedComponent, actualChildProps] ) } // 最后return出去 return hoistStatics(Connect, WrappedComponent)}
connect(mapStateToProps, mapDispatchToProps)(Component)

至此,我们搞明白了mapToProps函数是在什么时候执行的。再来回顾一下这部分的问题:如何向组件中注入state和dispatch,让我们从头梳理一下:

mapDispatchToProps用于建立组件和store.dispatch的映射关系。它可以是一个对象,也可以是一个函数,当它是一个函数的时候,第一个参数就是dispatch,第二个参数是组件自身的props。

需要根据ownProps决定是否要依据其变化重新计算这些函数的返回值,所以会以这些函数为基础,生成代理函数(proxy),代理函数的执行结果就是selector,上边挂载了dependsOnOwnProps属性,所以在selectorFactory内真正执行的时候,才有何时才去重新计算的依据。

先看代码,主要看initMapStateToProps 和 initMapDispatchToProps,看一下这段代码是什么意思。

回想一下平时使用React-Redux的时候,是不是只有被connect过并且传入了mapStateToProps的组件,会响应store的变化?所以,被更新的是被connect过的组件,而connect返回的是connectAdvanced,并且并且connectAdvanced会返回我们传入的组件,所以本质上是connectAdvanced内部依据store的变化更新自身,进而达到更新真正组件的目的。

根据谁变化(store)更新函数(checkForUpdates)将store和更新函数建立联系的Subscription

从mapToProps到selector

connectAdvanced是一个比较重量级的高阶函数,上边大致说了更新机制,但很多具体做法都是在connectAdvanced中实现的。源码很长,逻辑有一些复杂,我写了详细的注释。看的过程需要思考函数之间的调用关系以及目的,每个变量的意义,带着上边的结论,相信不难看懂。

至此,围绕常用的功能,React-Redux的源码就解读完了。回到文章最开始的三个问题:

subscription.onStateChange =this.notifySubscribers

将selector的执行结果作为props传入组件

mapStateToPropsFactories 和 mapDispatchToPropsFactories都是函数数组,其中的每个函数都会接收一个参数,为mapStateToProps或者mapDispatchToProps。而match函数的作用就是循环函数数组,mapStateToProps或者mapDispatchToProps作为每个函数的入参去执行,当此时的函数返回值不为假的时候,赋值给左侧。看一下match函数:

时间: 2019-09-01阅读: 218标签: redux写在前面

function selector(stateOrDispatch, ownProps) { ... return props}

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

说一下trySubscribe中根据不同情况判断直接使用store订阅,还是调用addNestedSub来实现内部订阅的原因。因为可能在一个应用中存在多个store,这里的判断是为了让不同的store订阅自己的listener,互不干扰。

阅读源码最好的办法是先确定问题,有目的性的去读。开始的时候我就是硬看,越看越懵,换了一种方式后收获了不少,相信你也是。

有了上边的结论,但想必大家都比较好奇究竟是怎么实现的,上边的几项工作都是协同完成的,最终的表象体现为下面几个问题:

import React from 'react'import { connect } from '../../react-redux-src'import { increaseAction, decreaseAction } from '../../actions/counter'import { Button } from 'antd'class Child extends React.Component { render() { const { increaseAction, decreaseAction, num } = this.props return div {num} Button onClick={() = increaseAction()}增加/Button Button onClick={() = decreaseAction()}减少/Button /div }}const mapStateToProps = (state, ownProps) = { const { counter } = state return { num: counter.num }}const mapDispatchToProps = (dispatch, ownProps) = { return { increaseAction: () = dispatch({ type: INCREASE }), decreaseAction: () = dispatch({ type: DECREASE }) }}export default connect(mapStateToProps, mapDispatchToProps)(Child)

首先,在connect的时候传入了mapStateToProps,mapDispatchToProps,mergeProps。再联想一下用法,这些函数内部可以接收到state或dispatch,以及ownProps,它们的返回值会传入组件的props。

将store从应用顶层注入后,该考虑如何向组件中注入state和dispatch了。

match循环的是一个函数数组,下面我们看一下这两个数组,分别是mapStateToPropsFactories 和 mapDispatchToPropsFactories:(下边源码中的whenMapStateToPropsIsFunction函数会放到后边讲解)

基于mapToProps生成selector

React-Redux的更新机制

import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'// 当mapStateToProps是函数的时候,调用wrapMapToPropsFuncexport function whenMapStateToPropsIsFunction(mapStateToProps) { return typeof mapStateToProps === 'function' ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps') : undefined}// 当mapStateToProps没有传的时候,调用wrapMapToPropsConstantexport function whenMapStateToPropsIsMissing(mapStateToProps) { return !mapStateToProps ? wrapMapToPropsConstant(() = ({})) : undefined}export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]

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

function match(arg, factories, name) { // 循环执行factories,这里的factories也就是mapStateToProps和mapDisPatchToProps两个文件中暴露出来的处理函数数组 for (let i = factories.length - 1; i = 0; i--) { // arg也就是mapStateToProps或者mapDispatchToProps // 这里相当于将数组内的每个函数之星了一遍,并将我们的mapToProps函数作为参数传进去 const result = factories[i](arg) if (result) return result }}

所以说了这么多,其实这只是Provider组件的更新,而不是应用内部某个被connect的组件的更新机制。我猜想应该有一个原因是考虑到了Provider有可能被嵌套使用,所以会有这种在Provider更新之后取新数据并重新订阅的做法,这样才能保证每次传给子组件的context是最新的。

Subscription

页面初始化执行的时候,dependsOnOwnProps为true,所以执行proxy.mapToProps(stateOrDispatch, ownProps),也就是detectFactoryAndVerify。在后续的执行过程中,会先将proxy的mapToProps赋值为我们传入connect的mapStateToProps或者mapDispatchToProps,然后在依照实际情况组件是否应该依赖自己的props赋值给dependsOnOwnProps。(注意,这个变量会在selectorFactory函数中作为组件是否根据自己的props变化执行mapToProps函数的依据)。

提供Subscrption类,实现订阅更新的逻辑提供Provider,将store传入Provider,便于下层组件从context或者props中获取store;并订阅store的变化,便于在store变化的时候更新Provider自身提供selector,负责将获取store中的stat和dispacth一些action的函数(或者直接就是dispatch)或者组件自己的props,并从中选择出组件需要的值,作为返回值

再提醒一下:下边的mapToProps指的是mapDispatchToProps或mapStateToProps

而这些都需要自己手动去做,React-Redux将上边的都封装起来了。让我们通过一段代码看一下React-Redux的用法:

注入的过程发生在connect的核心函数connectAdvanced之内,先忽略该函数内的其他过程,聚焦注入过程,简单看下源码

取到store的state或dispatch,以及ownProps执行selector将执行的返回值注入到组件

export function wrapMapToPropsConstant(getConstant) { return function initConstantSelector(dispatch, options) { const constant = getConstant(dispatch, options) function constantSelector() { return constant } constantSelector.dependsOnOwnProps = false return constantSelector }}
 import { bindActionCreators } from '../../redux-src' import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps' export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) { return typeof mapDispatchToProps === 'function' ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps') : undefined } // 当不传mapDispatchToProps时,默认向组件中注入dispatch export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) { return !mapDispatchToProps ? wrapMapToPropsConstant(dispatch = ({ dispatch })) : undefined } // 当传入的mapDispatchToProps是对象,利用bindActionCreators进行处理 详见redux/bindActionCreators.js export function whenMapDispatchToPropsIsObject(mapDispatchToProps) { return mapDispatchToProps  typeof mapDispatchToProps === 'object' ? wrapMapToPropsConstant(dispatch = bindActionCreators(mapDispatchToProps, dispatch)) : undefined } export default [ whenMapDispatchToPropsIsFunction, whenMapDispatchToPropsIsMissing, whenMapDispatchToPropsIsObject ]

Provider是怎么把store放入context中的

connect实际上是createConnect,createConnect也只是返回了一个connect函数,而connect函数返回了connectHOC的调用(也就是connectAdvanced的调用),再继续,connectAdvanced的调用最终会返回一个wrapWithConnect高阶组件,这个函数的参数是我们传入的组件。所以才有了connect平常的用法:

store变化,被connect的组件也会更新的

当传入的mapDispatchToProps是对象的时候,也是调用wrapMapToPropsConstant,根据前边的了解,这里注入到组件中的属性是bindActionCreators(mapDispatchToProps, dispatch)的执行结果。

mapDispatchToPropsFactories

我在读React-Redux源码的过程中,很自然的要去网上找一些参考文章,但发现这些文章基本都没有讲的很透彻,很多时候就是平铺直叙把API挨个讲一下,而且只讲某一行代码是做什么的,却没有结合应用场景和用法解释清楚为什么这么做,加上源码本身又很抽象,函数间的调用关系非常不好梳理清楚,最终结果就是越看越懵。我这次将尝试换一种解读方式,由最常见的用法入手,结合用法,提出问题,带着问题看源码里是如何实现的,以此来和大家一起逐渐梳理清楚React-Redux的运行机制。

欢迎关注我的公众号: 一口一个前端,不定期分享我所理解的前端知识

connect核心--connectAdvanced

当不传mapStateToProps的时候,当store变化的时候,不会引起组件UI的更新。

// 直接将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并作为返回值return出去export function impureFinalPropsSelectorFactory(){}export function pureFinalPropsSelectorFactory() { // 整个过程首次初始化的时候调用 function handleFirstCall(firstState, firstOwnProps) {} // 返回新的props function handleNewPropsAndNewState() { // 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并作为返回值return出去 } // 返回新的props function handleNewProps() { // 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并作为返回值return出去 } // 返回新的props function handleNewState() { // 将mapStateToProps,mapDispatchToProps,ownProps的执行结果合并作为返回值return出去 } // 后续的过程调用 function handleSubsequentCalls(nextState, nextOwnProps) {} return function pureFinalPropsSelector(nextState, nextOwnProps) { // 第一次渲染,调用handleFirstCall,之后的action派发行为会触发handleSubsequentCalls return hasRunAtLeastOnce ? handleSubsequentCalls(nextState, nextOwnProps) : handleFirstCall(nextState, nextOwnProps) }}// finalPropsSelectorFactory函数是在connectAdvaced函数内调用的selectorFactory函数export default function finalPropsSelectorFactory( dispatch, { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }) { const mapStateToProps = initMapStateToProps(dispatch, options) // 这里是wrapMapToProps.js中wrapMapToPropsFunc函数的柯里化调用,是改造 // 之后的mapStateToProps, 在下边返回的函数内还会再调用一次 const mapDispatchToProps = initMapDispatchToProps(dispatch, options) const mergeProps = initMergeProps(dispatch, options) // 根据是否传入pure属性,决定调用哪个生成selector的函数来计算传给组件的props。并将匹配到的函数赋值给selectorFactory const selectorFactory = options.pure ? pureFinalPropsSelectorFactory // 当props或state变化的时候,才去重新计算props : impureFinalPropsSelectorFactory // 直接重新计算props // 返回selectorFactory的调用 return selectorFactory( mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options )}

本文由网上澳门金莎娱乐发布于Web前端,转载请注明出处:带着问题看React-Redux源码

关键词: