react-redux原理分析

前端之家收集整理的这篇文章主要介绍了react-redux原理分析前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

reactredux并没有什么直接的联系. redux作为一个通用模块,主要还是用来处理应用中的state的变更,而展示层不一定是react.

但当我们希望在React + Redux的项目中将两者结合的更好,可以通过react-redux做连接。

本文结合react-redux的使用,分析其实现原理。

react-redux

react-redux是一个轻量级的封装库,核心方法只有两个:

  • Provider

  • connect

  • @H_404_35@

    下面我们来逐个分析其作用

    Provider

    完整源码请戳这里

    Provider模块的功能并不复杂,主要分为以下两点:

    • 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件

    • 接收Redux的store作为props,通过context对象传递给子孙组件上的connect

    • @H_404_35@
      import { Component,Children } from 'react'
      import PropTypes from 'prop-types'
      import { storeShape,subscriptionShape } from '../utils/PropTypes'
      import warning from '../utils/warning'
      
      let didWarnAboutReceivingStore = false
      function warnAboutReceivingStore() {
        if (didWarnAboutReceivingStore) {
          return
        }
        didWarnAboutReceivingStore = true
      }
      
      export function createProvider(storeKey = 'store',subKey) {
          const subscriptionKey = subKey || `${storeKey}Subscription`
      
          class Provider extends Component {
              getChildContext() {
                return { [storeKey]: this[storeKey],[subscriptionKey]: null }
              }
      
              constructor(props,context) {
                super(props,context)
                this[storeKey] = props.store;
              }
      
              render() {
                return Children.only(this.props.children)
              }
          }
      
          if (process.env.NODE_ENV !== 'production') {
            Provider.prototype.componentWillReceiveProps = function (nextProps) {
              if (this[storeKey] !== nextProps.store) {
                warnAboutReceivingStore()
              }
            }
          }
      
          return Provider
      }
      
      export default createProvider()

      1.1 封装原应用

      render方法中,渲染了其子级元素,使整个应用成为Provider的子组件.

      1. this.props.children是react内置在this.props上的对象,用于获取当前组件的所有子组件.

      2. Children为react内部定义的顶级对象,该对象封装了一些方便操作字组件的方法. Children.only用于获取仅有的一个子组件,
        没有或者超过一个均会报错. 所以注意: 确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置

      1.2 传递store

      1. constructor方法: Provider初始化时,获取到props中的store对象;

      2. getChildContext方法: 将外部的store对象放入context对象中,使子孙组件上的connect可以直接访问到context对象中的store。

      context可以使子孙组件直接获取父级组件中的数据或方法,而无需一层一层通过props向下传递。context对象相当于一个独立的空间,父组件通过getChildContext()向该空间内写值;定义了contextTypes验证的子孙组件可以通过this.context.xxx,从context对象中读取xxx字段的值

      1.3 小结

      总而言之,Provider模块的功能很简单,从最外部封装了整个应用,并向connect模块传递store
      而最核心的功能connect模块中。

      connect

      正如这个模块的命名,connect模块才是真正连接了ReactRedux

      现在,我们可以先回想一下Redux是怎样运作的:首先需要注册一个全局唯一的store对象,用来维护整个应用的state;当要变更state时,我们会dispatch一个action,reducer根据action更新相应的state。

      下面我们再考虑一下使用react-redux时,我们做了什么:

      import React from "react"
          import ReactDOM from "react-dom"
          import { bindActionCreators } from "redux"
          import {connect} from "react-redux"
          
          class xxxComponent extends React.Component{
              constructor(props){
                  super(props)
              }
              componentDidMount(){
                  this.props.aActions.xxx1();
              }
              render (
                  <div>
                      {this.props.$$aProps}
                  </div>
              )
          }
          
          export default connect(
              state => ({
                  $$aProps: state.$$aProps,$$bProps: state.$$bProps,// ...
              }),dispatch => ({
                  aActions: bindActionCreators(AActions,dispatch),bActions: bindActionCreators(BActions,// ...
              })
          )(xxxComponent)

      由export的component对象进行如下猜想:
      1、使用了react-reduxconnect后,我们导出的对象不再是原先定义的xxx Component,而是通过connect包裹后的新React.Component对象。
      connect执行后返回一个函数(wrapWithConnect),那么其内部势必形成了闭包。而wrapWithConnect执行后,必须要返回一个ReactComponent对象,才能保证原代码逻辑可以正常运行,而这个ReactComponent对象通过render原组件,形成对原组件的封装。
      2、渲染页面需要store tree中的state片段,变更state需要dispatch一个action,而这两部分,都是从this.props获取。故在我们调用connect时,作为参数传入的stateaction,便在connect内部进行合并,通过props的方式传递给包裹后的ReactComponent
      好了,以上只是我们的猜测,下面看具体实现,完整代码请戳这里.

      connect(
          mapStateToProps(state,ownProps) => stateProps: object,mapDispatchToProps(dispatch,ownProps) => dispatchProps: object,mergeProps(stateProps,dispatchProps,ownProps) => props: Object,options: object
      ) => (
          component
      ) => component

      再来看下connect函数体结构,我们摘取核心步骤进行描述:

      export default function connect(mapStateToProps,mapDispatchToProps,mergeProps,options = {}) {
          // 参数处理
          // ...
          return function wrapWithConnect(WrappedComponent) {
              
              class Connect extends Component {
                  constructor(props,context) {
                      super(props,context)
                      this.store = props.store || context.store;
                      const storeState = this.store.getState()
                      this.state = { storeState }
                  }
                  // 周期方法及操作方法
                  // ...
                  render(){
                      this.renderedElement = createElement(WrappedComponent,this.mergedProps //mearge stateProps,props
                      )
                      return this.renderedElement;
                  }
              }
              return hoistStatics(Connect,WrappedComponent);
          }
      }

      其实已经基本印证了我们的猜测:
      1、connect通过context获取Provider中的store,通过store.getState()获取整个store tree 上所有state
      2、connect模块的返回值wrapWithConnectfunction
      3、wrapWithConnect返回一个ReactComponent对象ConnectConnect重新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps,mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给WrappedComponent
      下面我们结合代码进行分析一下每个函数的意义。

      mapStateToProps

      mapStateToProps(state,props)必须是一个函数.
      参数statestore tree中所有state,参数props为通过组件Connect传入的props.
      返回值表示需要mergeprops中的state.

      mapDispatchToProps

      mapDispatchToProps(dispatch,props)可以是一个函数,也可以是一个对象.
      参数dispatchstore.dispatch函数,参数props为通过组件Connect传入的props.
      返回值表示需要mergeprops中的action.

      mergeProps(一般不用)

      mergeProps是一个函数,定义了mapState,mapDispatchthis.props的合并规则.

      options(一般不用)

      options是一个对象,包含purewithRef两个属性
      pure: 表示是否开启pure优化,默认值为true.
      withRef: withRef用来给包装在里面的组件一个ref,可以通过getWrappedInstance方法获取这个ref,默认为false。

      React如何响应Store变化

      文章一开始我们也提到React其实跟Redux没有直接联系,也就是说,Redux中dispatch触发store中state变化,并不会导致React重新渲染. react-redux才是真正触发React重新渲染的模块,那么这一过程怎样实现的呢?
      刚刚提到connect模块返回一个wrapConnect函数,此函数中又返回了一个Connect组件. Connect组件的功能有以下两点:

      • 包装组件,将state和action通过props的方式传入到原组件内部

      • 监听store tree变化,使其包装的原组件可以响应state变化
        下面我们主要分析下第二点

      • @H_404_35@

        如何注册监听

        在redux中,可以通过store.subscribe(listener)注册一个监听器.listener会在store tree更新后执行.以下代码为Connect组件内部,向store tree注册listener的过程。

        trySubscribe() {
            if (!this.unsubscribe) {
              this.unsubscribe = this.parentSub
                ? this.parentSub.addNestedSub(this.onStateChange)
                : this.store.subscribe(this.onStateChange)
         
              this.listeners = createListenerCollection()
            }
          }

        何时注册

        componentDidMount() {
                ...
                this.subscription.trySubscribe()
                ...
            }

        可以看到,当Connect组件加载到页面后,当前组件开始监听store tree变化

        何时注销

        当前Connect组件销毁后,我们希望其中注册的listener也一并销毁,避免性能问题。此时可以在Connect的componentWillUnmount周期函数中执行这一过程。

        componentWillUnmount() {
                  if (this.subscription) this.subscription.tryUnsubscribe()
                  ...
              }

        变更处理逻辑

        有了触发组件更新的时机,我们下面主要看下,组件是通过何种方式触发重新渲染

        onStateChange() {
                ...
                if (!this.selector.shouldComponentUpdate) {
                  ...
                } else {
                  ...
                  this.setState(dummyState) // dummyState = {},仅仅是为了触发更新
                }
              }

        小结

        可以看到,react-redux的核心功能都在connect模块中,理解好这个模块,有助于我们更好的使用react-redux处理业务问题,优化代码性能

猜你在找的React相关文章