react-redux原理分析

寫在前面

以前寫了一篇分析Redux中Store實現的文章(詳見:Redux原理(一):Store實現分析),忽然意識到,其實React與Redux並無什麼直接的聯繫。Redux做爲一個通用模塊,主要仍是用來處理應用中state的變動,而展現層不必定是React。
但當咱們但願在React+Redux的項目中將二者結合的更好,能夠經過react-redux作鏈接。
本文結合react-redux的使用,分析其實現原理。html

react-redux

react-redux是一個輕量級的封裝庫,核心方法只有兩個:react

  • Provider
  • connect

下面咱們來逐個分析其做用git

Provider

完整源碼請戳這裏
Provider模塊的功能並不複雜,主要分爲如下兩點:github

  • 在原應用組件上包裹一層,使原來整個應用成爲Provider的子組件
  • 接收Redux的store做爲props,經過context對象傳遞給子孫組件上的connect

下面看下具體代碼:
編程

封裝原應用

[31-34] render方法中,渲染了其子級元素,使整個應用成爲Provider的子組件。
一、this.props.children是react內置在this.props上的對象,用於獲取當前組件的全部子組件
二、Children爲react內部定義的頂級對象,該對象上封裝了一些方便操做子組件的方法。Children.only用於獲取僅有的一個子組件,沒有或超過一個均會報錯。故須要注意:確保Provider組件的直接子級爲單個封閉元素,切勿多個組件平行放置。redux

傳遞store

[26-29] Provider初始化時,獲取到props中的store對象;
[22-24] 將外部的store對象放入context對象中,使子孫組件上的connect能夠直接訪問到context對象中的store。
一、context可使子孫組件直接獲取父級組件中的數據或方法,而無需一層一層經過props向下傳遞。context對象至關於一個獨立的空間,父組件經過getChildContext()向該空間內寫值;定義了contextTypes驗證的子孫組件能夠經過this.context.xxx,從context對象中讀取xxx字段的值。緩存

小結

總而言之,Provider模塊的功能很簡單,從最外部封裝了整個應用,並向connect模塊傳遞store。
而最核心的功能在connect模塊中。閉包

connect

正如這個模塊的命名,connect模塊纔是真正鏈接了React和Redux。
如今,咱們能夠先回想一下Redux是怎樣運做的:首先須要註冊一個全局惟一的store對象,用來維護整個應用的state;當要變動state時,咱們會dispatch一個action,reducer根據action更新相應的state。
下面咱們再考慮一下使用react-redux時,咱們作了什麼:app

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=>{
        return {
            $$aProps:state.$$aProps,
            $$bProps:state.$$bProps,
            // ...
        }
    },
    dispatch=>{
        return {
            aActions:bindActionCreators(AActions,dispatch),
            bActions:bindActionCreators(BActions,dispatch),
            // ...
        }
    }
)(xxxComponent)

經過以上代碼,咱們能夠概括出如下信息:框架

一、使用了react-redux後,咱們導出的對象再也不是原先定義的xxxComponent,而是經過connect包裹後的新React.Component對象。
connect執行後返回一個函數(wrapWithConnect),那麼其內部勢必造成了閉包。而wrapWithConnect執行後,必需要返回一個ReactComponent對象,才能保證原代碼邏輯能夠正常運行,而這個ReactComponent對象經過render原組件,造成對原組件的封裝。
二、渲染頁面須要store tree中的state片斷,變動state須要dispatch一個action,而這兩部分,都是從this.props獲取。故在咱們調用connect時,做爲參數傳入的state和action,便在connect內部進行合併,經過props的方式傳遞給包裹後的ReactComponent。
好了,以上只是咱們的猜想,下面看具體實現,完整代碼請戳這裏
connect完整函數聲明以下:

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, dispatchProps, props
                )
                return this.renderedElement;
            }
        }
        return hoistStatics(Connect, WrappedComponent);
    }
}

其實已經基本印證了咱們的猜想:
一、connect經過context獲取Provider中的store,經過store.getState()獲取整個store tree 上全部state。
二、connect模塊的返回值wrapWithConnect爲function。
三、wrapWithConnect返回一個ReactComponent對象Connect,Connect從新render外部傳入的原組件WrappedComponent,並把connect中傳入的mapStateToProps, mapDispatchToProps與組件上原有的props合併後,經過屬性的方式傳給WrappedComponent。

下面咱們結合代碼進行分析一下每一個函數的意義。

mapStateToProps

mapStateToProps(state,props)必須是一個函數。
參數state爲store tree中全部state,參數props爲經過組件Connect傳入的props。
返回值表示須要merge進props中的state。

以上代碼用來計算待merge的state,[104-105]經過調用finalMapStateToProps獲取merge state。其中做爲參數的state經過store.getState()獲取,很明顯是store tree中全部的state

mapDispatchToProps

mapDispatchToProps(dispatch, props)能夠是一個函數,也能夠是一個對象。
參數dispatch爲store.dispatch()函數,參數props爲經過組件Connect傳入的props。
返回值表示須要merge進props中的action。

以上代碼用來計算待merge的action,代碼邏輯與計算state十分類似。做爲參數的dispatch就是store.dispatch

mergeProps

mergeProps是一個函數,定義了mapState,mapDispatchthis.props的合併規則,默認合併規則以下:

須要注意的是:若是三個對象中字段出現同名,前者會被後者覆蓋

若是經過connect註冊了mergeProps方法,以上代碼會使用mergeProps定義的規則進行合併,mergeProps合併後的結果,會經過props傳入Connect組件。

options

options是一個對象,包含purewithRef兩個屬性

pure

表示是否開啓pure優化,默認值爲true

withRef

withRef用來給包裝在裏面的組件一個ref,能夠經過getWrappedInstance方法來獲取這個ref,默認爲false。

React如何響應store變化

文章一開始咱們也提到React其實跟Redux沒有直接聯繫,也就是說,Redux中dispatch觸發store tree中state變化,並不會致使React從新渲染。
react-redux纔是真正觸發React從新渲染的模塊,那麼這一過程是怎樣實現的呢?
剛剛提到,connect模塊返回一個wrapWithConnect函數,wrapWithConnect函數中又返回了一個Connect組件。Connect組件的功能有如下兩點:
一、包裝原組件,將state和action經過props的方式傳入到原組件內部
二、監聽store tree變化,使其包裝的原組件能夠響應state變化
下面咱們主要分析下第二點:

如何註冊監聽

Redux中,能夠經過store.subscribe(listener)註冊一個監聽器。listener會在store tree更新後執行。

以上代碼爲Connect組件內部,向store tree註冊listener的過程。
[199] 調用store.subscribe註冊一個名爲handleChange的listener,返回值爲當前listener的註銷函數。

什麼時候註冊

能夠看到,當Connect組件加載到頁面後,當前組件開始監聽store tree變化。

什麼時候註銷

噹噹前Connect組件銷燬後,咱們但願其中註冊的listener也一併銷燬,避免性能問題。此時能夠在Connect的componentWillUnmount周期函數中執行這一過程。

變動處理邏輯

有了觸發組件更新的時機,咱們下面主要看下,組件是經過何種方式觸發從新渲染

[244-245] Connect組件在初始化時,就已經在this.state中緩存了store tree中state的狀態。這兩行分別取出當前state狀態和變動前state狀態進行比較
[262] 比較過程暫時略過,這一行將最終store tree中state經過this.setState()更新到Connect內部的state中,而this.setState()方法正好能夠觸發Connect及其子組件的從新渲染。

小結

能夠看到,react-redux的核心功能都在connect模塊中,理解好這個模塊,有助於咱們更好的使用react-redux處理業務問題,優化代碼性能。

總結

本文經過分析react-redux源碼,詳細介紹了Provider和connect模塊,從新梳理了Reat、redux、react-redux三者間的關係。 我的以爲多看看源碼仍是頗有好處的,一方面能夠加深本身對已使用框架的理解;再一方面能夠學到一些優秀的編程思路。 技術這條路上,懂的越多,不懂的也就越多,學無止境,戒驕戒躁。

相關文章
相關標籤/搜索