天天看點

手寫一個React-Redux,玩轉React的Context API

上一篇文章我們手寫了一個Redux,但是單純的Redux隻是一個狀态機,是沒有UI呈現的,是以一般我們使用的時候都會配合一個UI庫,比如在React中使用Redux就會用到

React-Redux

這個庫。這個庫的作用是将Redux的狀态機和React的UI呈現綁定在一起,當你

dispatch action

改變

state

的時候,會自動更新頁面。本文還是從它的基本使用入手來自己寫一個

React-Redux

,然後替換官方的NPM庫,并保持功能一緻。

本文全部代碼已經上傳GitHub,大家可以拿下來玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux

基本用法

下面這個簡單的例子是一個計數器,跑起來效果如下:

要實作這個功能,首先我們要在項目裡面添加

react-redux

庫,然後用它提供的

Provider

包裹整個

React

App的根元件:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store from './store'
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
           

上面代碼可以看到我們還給

Provider

提供了一個參數

store

,這個參數就是Redux的

createStore

生成的

store

,我們需要調一下這個方法,然後将傳回的

store

傳進去:

import { createStore } from 'redux';
import reducer from './reducer';

let store = createStore(reducer);

export default store;
           

上面代碼中

createStore

的參數是一個

reducer

,是以我們還要寫個

reducer

:

const initState = {
  count: 0
};

function reducer(state = initState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, count: state.count + 1};
    case 'DECREMENT':
      return {...state, count: state.count - 1};
    case 'RESET':
      return {...state, count: 0};
    default:
      return state;
  }
}

export default reducer;
           

這裡的

reduce

會有一個初始

state

,裡面的

count

,同時他還能處理三個

action

,這三個

action

對應的是UI上的三個按鈕,可以對

state

裡面的計數進行加減和重置。到這裡其實我們

React-Redux

的接入和

Redux

資料的組織其實已經完成了,後面如果要用

Redux

裡面的資料的話,隻需要用

connect

API将對應的

state

和方法連接配接到元件裡面就行了,比如我們的計數器元件需要

count

這個狀态和加一,減一,重置這三個

action

,我們用

connect

将它連接配接進去就是這樣:

import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement, reset } from './actions';

function Counter(props) {
  const { 
    count,
    incrementHandler,
    decrementHandler,
    resetHandler
   } = props;

  return (
    <>
      <h3>Count: {count}</h3>
      <button onClick={incrementHandler}>計數+1</button>
      <button onClick={decrementHandler}>計數-1</button>
      <button onClick={resetHandler}>重置</button>
    </>
  );
}

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    incrementHandler: () => dispatch(increment()),
    decrementHandler: () => dispatch(decrement()),
    resetHandler: () => dispatch(reset()),
  }
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)
           

上面代碼可以看到

connect

是一個高階函數,他的第一階會接收

mapStateToProps

mapDispatchToProps

兩個參數,這兩個參數都是函數。

mapStateToProps

可以自定義需要将哪些

state

連接配接到目前元件,這些自定義的

state

可以在元件裡面通過

props

拿到。

mapDispatchToProps

方法會傳入

dispatch

函數,我們可以自定義一些方法,這些方法可以調用

dispatch

dispatch action

,進而觸發

state

的更新,這些自定義的方法也可以通過元件的

props

拿到,

connect

的第二階接收的參數是一個元件,我們可以猜測這個函數的作用就是将前面自定義的

state

和方法注入到這個元件裡面,同時要傳回一個新的元件給外部調用,是以

connect

其實也是一個高階元件。

到這裡我們彙總來看下我們都用到了哪些API,這些API就是我們後面要手寫的目标:

Provider

: 用來包裹根元件的元件,作用是注入

Redux

store

createStore

Redux

用來建立

store

的核心方法,我們另一篇文章已經手寫過了。

connect

:用來将

state

dispatch

注入給需要的元件,傳回一個新元件,他其實是個高階元件。

是以

React-Redux

核心其實就兩個API,而且兩個都是元件,作用還很類似,都是往元件裡面注入參數,

Provider

是往根元件注入

store

connect

是往需要的元件注入

state

dispatch

在手寫之前我們先來思考下,為什麼

React-Redux

要設計這兩個API,假如沒有這兩個API,隻用

Redux

可以嗎?當然是可以的!其實我們用

Redux

的目的不就是希望用它将整個應用的狀态都儲存下來,每次操作隻用

dispatch action

去更新狀态,然後UI就自動更新了嗎?那我從根元件開始,每一級都把

store

傳下去不就行了嗎?每個子元件需要讀取狀态的時候,直接用

store.getState()

就行了,更新狀态的時候就

store.dispatch

,這樣其實也能達到目的。但是,如果這樣寫,子元件如果嵌套層數很多,每一級都需要手動傳入

store

,比較醜陋,開發也比較繁瑣,而且如果某個新同學忘了傳

store

,那後面就是一連串的錯誤了。是以最好有個東西能夠将

store

全局的注入元件樹,而不需要一層層作為

props

傳遞,這個東西就是

Provider

!而且如果每個元件都獨立依賴

Redux

會破壞

React

的資料流向,這個我們後面會講到。

React的Context API

React其實提供了一個全局注入變量的API,這就是context api。假如我現在有一個需求是要給我們所有元件傳一個文字顔色的配置,我們的顔色配置在最頂級的元件上,當這個顔色改變的時候,下面所有元件都要自動應用這個顔色。那我們可以使用context api注入這個配置:

先使用

React.createContext

建立一個context

// 我們使用一個單獨的檔案來調用createContext
// 因為這個傳回值會被Provider和Consumer在不同的地方引用
import React from 'react';

const TestContext = React.createContext();

export default TestContext;
           

使用

Context.Provider

包裹根元件

建立好了context,如果我們要傳遞變量給某些元件,我們需要在他們的根元件上加上

TestContext.Provider

,然後将變量作為

value

參數傳給

TestContext.Provider

import TestContext from './TestContext';

const setting = {
  color: '#d89151'
}

ReactDOM.render(
  <TestContext.Provider value={setting}>
  	<App />
  </TestContext.Provider>,
  document.getElementById('root')
);
           

Context.Consumer

接收參數

上面我們使用

Context.Provider

将參數傳遞進去了,這樣被

Context.Provider

包裹的所有子元件都可以拿到這個變量,隻是拿這個變量的時候需要使用

Context.Consumer

包裹,比如我們前面的

Counter

元件就可以拿到這個顔色了,隻需要将它傳回的

JSX

Context.Consumer

包裹一下就行:

// 注意要引入同一個Context
import TestContext from './TestContext';

// ... 中間省略n行代碼 ...
// 傳回的JSX用Context.Consumer包裹起來
// 注意Context.Consumer裡面是一個方法,這個方法就可以通路到context參數
// 這裡的context也就是前面Provider傳進來的setting,我們可以拿到上面的color變量
return (
    <TestContext.Consumer>
      {context => 
        <>
          <h3 style={{color:context.color}}>Count: {count}</h3>
          <button onClick={incrementHandler}>計數+1</button>&nbsp;&nbsp;
          <button onClick={decrementHandler}>計數-1</button>&nbsp;&nbsp;
          <button onClick={resetHandler}>重置</button>
        </>
      }
    </TestContext.Consumer>
  );
           

上面代碼我們通過

context

傳遞了一個全局配置,可以看到我們文字顔色已經變了:

useContext

除了上面的

Context.Consumer

可以用來接收

context

參數,新版React還有

useContext

這個hook可以接收context參數,使用起來更簡單,比如上面代碼可以這樣寫:

const context = useContext(TestContext);

return (
    <>
      <h3 style={{color:context.color}}>Count: {count}</h3>
      <button onClick={incrementHandler}>計數+1</button>&nbsp;&nbsp;
      <button onClick={decrementHandler}>計數-1</button>&nbsp;&nbsp;
      <button onClick={resetHandler}>重置</button>
    </>
);
           

是以我們完全可以用

context api

來傳遞

redux store

,現在我們也可以猜測

React-Redux

Provider

其實就是包裝了

Context.Provider

,而傳遞的參數就是

redux store

,而

React-Redux

connect

HOC其實就是包裝的

Context.Consumer

或者

useContext

。我們可以按照這個思路來自己實作下

React-Redux

了。

手寫

Provider

上面說了

Provider

用了

context api

,是以我們要先建一個

context

檔案,導出需要用的

context

// Context.js
import React from 'react';

const ReactReduxContext = React.createContext();

export default ReactReduxContext;
           

這個檔案很簡單,建立一個

context

再導出就行了,對應的源碼看這裡。

然後将這個

context

應用到我們的

Provider

元件裡面:

import React from 'react';
import ReactReduxContext from './Context';

function Provider(props) {
  const {store, children} = props;

  // 這是要傳遞的context
  const contextValue = { store };

  // 傳回ReactReduxContext包裹的元件,傳入contextValue
  // 裡面的内容就直接是children,我們不動他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    </ReactReduxContext.Provider>
  )
}
           

Provider

的元件代碼也不難,直接将傳進來的

store

放到

context

上,然後直接渲染

children

就行,對應的源碼看這裡。

connect

基本功能

其實

connect

才是React-Redux中最難的部分,裡面功能複雜,考慮的因素很多,想要把它搞明白我們需要一層一層的來看,首先我們實作一個隻具有基本功能的

connect

import React, { useContext } from 'react';
import ReactReduxContext from './Context';

// 第一層函數接收mapStateToProps和mapDispatchToProps
function connect(mapStateToProps, mapDispatchToProps) {
  // 第二層函數是個高階元件,裡面擷取context
  // 然後執行mapStateToProps和mapDispatchToProps
  // 再将這個結果組合使用者的參數作為最終參數渲染WrappedComponent
  // WrappedComponent就是我們使用connext包裹的自己的元件
  return function connectHOC(WrappedComponent) {

    function ConnectFunction(props) {
      // 複制一份props到wrapperProps
      const { ...wrapperProps } = props;

      // 擷取context的值
      const context = useContext(ReactReduxContext);

      const { store } = context;  // 解構出store
      const state = store.getState();   // 拿到state

      // 執行mapStateToProps和mapDispatchToProps
      const stateProps = mapStateToProps(state);
      const dispatchProps = mapDispatchToProps(store.dispatch);

      // 組裝最終的props
      const actualChildProps = Object.assign({}, stateProps, dispatchProps, wrapperProps);

      // 渲染WrappedComponent
      return <WrappedComponent {...actualChildProps}></WrappedComponent>
    }

    return ConnectFunction;
  }
}

export default connect;
           

觸發更新

用上面的

Provider

connect

替換官方的

react-redux

其實已經可以渲染出頁面了,但是點選按鈕還不會有反應,因為我們雖然通過

dispatch

改變了

store

中的

state

,但是這種改變并沒有觸發我們元件的更新。之前Redux那篇文章講過,可以用

store.subscribe

來監聽

state

的變化并執行回調,我們這裡需要注冊的回調是檢查我們最終給

WrappedComponent

props

有沒有變化,如果有變化就重新渲染

ConnectFunction

,是以這裡我們需要解決兩個問題:

  1. 當我們

    state

    變化的時候檢查最終給到

    ConnectFunction

    的參數有沒有變化
  2. 如果這個參數有變化,我們需要重新渲染

    ConnectFunction

檢查參數變化

要檢查參數的變化,我們需要知道上次渲染的參數和本地渲染的參數,然後拿過來比一下就知道了。為了知道上次渲染的參數,我們可以直接在

ConnectFunction

裡面使用

useRef

将上次渲染的參數記錄下來:

// 記錄上次渲染參數
const lastChildProps = useRef();
useLayoutEffect(() => {
  lastChildProps.current = actualChildProps;
}, []);
           

注意

lastChildProps.current

是在第一次渲染結束後指派,而且需要使用

useLayoutEffect

來保證渲染後立即同步執行。

因為我們檢測參數變化是需要重新計算

actualChildProps

,計算的邏輯其實都是一樣的,我們将這塊計算邏輯抽出來,成為一個單獨的方法

childPropsSelector

function childPropsSelector(store, wrapperProps) {
  const state = store.getState();   // 拿到state

  // 執行mapStateToProps和mapDispatchToProps
  const stateProps = mapStateToProps(state);
  const dispatchProps = mapDispatchToProps(store.dispatch);

  return Object.assign({}, stateProps, dispatchProps, wrapperProps);
}
           

然後就是注冊

store

的回調,在裡面來檢測參數是否變了,如果變了就強制更新目前元件,對比兩個對象是否相等,

React-Redux

裡面是采用的

shallowEqual

,也就是淺比較,也就是隻對比一層,如果你

mapStateToProps

傳回了好幾層結構,比如這樣:

{
  stateA: {
    value: 1
  }
}
           

你去改了

stateA.value

是不會觸發重新渲染的,

React-Redux

這樣設計我想是出于性能考慮,如果是深比較,比如遞歸去比較,比較浪費性能,而且如果有循環引用還可能造成死循環。采用淺比較就需要使用者遵循這種範式,不要傳入多層結構,這點在官方文檔中也有說明。我們這裡直接抄一個它的淺比較:

// shallowEqual.js 
function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false
    }
  }

  return true
}
           

在回調裡面檢測參數變化:

// 注冊回調
store.subscribe(() => {
  const newChildProps = childPropsSelector(store, wrapperProps);
  // 如果參數變了,記錄新的值到lastChildProps上
  // 并且強制更新目前元件
  if(!shallowEqual(newChildProps, lastChildProps.current)) {
    lastChildProps.current = newChildProps;

    // 需要一個API來強制更新目前元件
  }
});
           

強制更新

要強制更新目前元件的方法不止一個,如果你是用的

Class

元件,你可以直接

this.setState({})

,老版的

React-Redux

就是這麼幹的。但是新版

React-Redux

用hook重寫了,那我們可以用React提供的

useReducer

useState

hook,

React-Redux

源碼用了

useReducer

,為了跟他保持一緻,我也使用

useReducer

function storeStateUpdatesReducer(count) {
  return count + 1;
}

// ConnectFunction裡面
function ConnectFunction(props) {
  // ... 前面省略n行代碼 ... 
  
  // 使用useReducer觸發強制更新
  const [
    ,
    forceComponentUpdateDispatch
  ] = useReducer(storeStateUpdatesReducer, 0);
  // 注冊回調
  store.subscribe(() => {
    const newChildProps = childPropsSelector(store, wrapperProps);
    if(!shallowEqual(newChildProps, lastChildProps.current)) {
      lastChildProps.current = newChildProps;
      forceComponentUpdateDispatch();
    }
  });
  
  // ... 後面省略n行代碼 ...
}
           

connect

這塊代碼主要對應的是源碼中

connectAdvanced

這個類,基本原理和結構跟我們這個都是一樣的,隻是他寫的更靈活,支援使用者傳入自定義的

childPropsSelector

和合并

stateProps, dispatchProps, wrapperProps

的方法。有興趣的朋友可以去看看他的源碼:https://github.com/reduxjs/react-redux/blob/master/src/components/connectAdvanced.js

到這裡其實已經可以用我們自己的

React-Redux

替換官方的了,計數器的功能也是支援了。但是下面還想講一下

React-Redux

是怎麼保證元件的更新順序的,因為源碼中很多代碼都是在處理這個。

保證元件更新順序

前面我們的

Counter

元件使用

connect

連接配接了

redux store

,假如他下面還有個子元件也連接配接到了

redux store

,我們就要考慮他們的回調的執行順序的問題了。我們知道React是單向資料流的,參數都是由父元件傳給子元件的,現在引入了

Redux

,即使父元件和子元件都引用了同一個變量

count

,但是子元件完全可以不從父元件拿這個參數,而是直接從

Redux

拿,這樣就打破了

React

本來的資料流向。在

父->子

這種單向資料流中,如果他們的一個公用變量變化了,肯定是父元件先更新,然後參數傳給子元件再更新,但是在

Redux

裡,資料變成了

Redux -> 父,Redux -> 子

完全可以根據

Redux

的資料進行獨立更新,而不能完全保證父級先更新,子級再更新的流程。是以

React-Redux

花了不少功夫來手動保證這個更新順序,

React-Redux

保證這個更新順序的方案是在

redux store

外,再單獨建立一個監聽者類

Subscription

  1. Subscription

    負責處理所有的

    state

    變化的回調
  2. 如果目前連接配接

    redux

    的元件是第一個連接配接

    redux

    的元件,也就是說他是連接配接

    redux

    的根元件,他的

    state

    回調直接注冊到

    redux store

    ;同時建立一個

    Subscription

    執行個體

    subscription

    通過

    context

    傳遞給子級。
  3. redux

    的元件不是連接配接

    redux

    的根元件,也就是說他上面有元件已經注冊到

    redux store

    了,那麼他可以拿到上面通過

    context

    傳下來的

    subscription

    ,源碼裡面這個變量叫

    parentSub

    ,那目前元件的更新回調就注冊到

    parentSub

    上。同時再建立一個

    Subscription

    執行個體,替代

    context

    上的

    subscription

    ,繼續往下傳,也就是說他的子元件的回調會注冊到目前

    subscription

    上。
  4. state

    變化了,根元件注冊到

    redux store

    上的回調會執行更新根元件,同時根元件需要手動執行子元件的回調,子元件回調執行會觸發子元件更新,然後子元件再執行自己

    subscription

    上注冊的回調,觸發孫子元件更新,孫子元件再調用注冊到自己

    subscription

    上的回調。。。這樣就實作了從根元件開始,一層一層更新子元件的目的,保證了

    父->子

    這樣的更新順序。

Subscription

是以我們先建立一個

Subscription

類:

export default class Subscription {
  constructor(store, parentSub) {
    this.store = store
    this.parentSub = parentSub
    this.listeners = [];        // 源碼listeners是用連結清單實作的,我這裡簡單處理,直接數組了

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
  }

  // 子元件注冊回調到Subscription上
  addNestedSub(listener) {
    this.listeners.push(listener)
  }

  // 執行子元件的回調
  notifyNestedSubs() {
    const length = this.listeners.length;
    for(let i = 0; i < length; i++) {
      const callback = this.listeners[i];
      callback();
    }
  }

  // 回調函數的包裝
  handleChangeWrapper() {
    if (this.onStateChange) {
      this.onStateChange()
    }
  }

  // 注冊回調的函數
  // 如果parentSub有值,就将回調注冊到parentSub上
  // 如果parentSub沒值,那目前元件就是根元件,回調注冊到redux store上
  trySubscribe() {
      this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : this.store.subscribe(this.handleChangeWrapper)
  }
}
           

Subscription

對應的源碼看這裡。

改造

Provider

然後在我們前面自己實作的

React-Redux

裡面,我們的根元件始終是

Provider

,是以

Provider

需要執行個體化一個

Subscription

并放到

context

上,而且每次

state

更新的時候需要手動調用子元件回調,代碼改造如下:

import React, { useMemo, useEffect } from 'react';
import ReactReduxContext from './Context';
import Subscription from './Subscription';

function Provider(props) {
  const {store, children} = props;

  // 這是要傳遞的context
  // 裡面放入store和subscription執行個體
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    // 注冊回調為通知子元件,這樣就可以開始層級通知了
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  // 拿到之前的state值
  const previousState = useMemo(() => store.getState(), [store])

  // 每次contextValue或者previousState變化的時候
  // 用notifyNestedSubs通知子元件
  useEffect(() => {
    const { subscription } = contextValue;
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
  }, [contextValue, previousState])

  // 傳回ReactReduxContext包裹的元件,傳入contextValue
  // 裡面的内容就直接是children,我們不動他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    </ReactReduxContext.Provider>
  )
}

export default Provider;
           

connect

有了

Subscription

類,

connect

就不能直接注冊到

store

了,而是應該注冊到父級

subscription

上,更新的時候除了更新自己還要通知子元件更新。在渲染包裹的元件時,也不能直接渲染了,而是應該再次使用

Context.Provider

包裹下,傳入修改過的

contextValue

,這個

contextValue

裡面的

subscription

應該替換為自己的。改造後代碼如下:

import React, { useContext, useRef, useLayoutEffect, useReducer } from 'react';
import ReactReduxContext from './Context';
import shallowEqual from './shallowEqual';
import Subscription from './Subscription';

function storeStateUpdatesReducer(count) {
  return count + 1;
}

function connect(
  mapStateToProps = () => {}, 
  mapDispatchToProps = () => {}
  ) {
  function childPropsSelector(store, wrapperProps) {
    const state = store.getState();   // 拿到state

    // 執行mapStateToProps和mapDispatchToProps
    const stateProps = mapStateToProps(state);
    const dispatchProps = mapDispatchToProps(store.dispatch);

    return Object.assign({}, stateProps, dispatchProps, wrapperProps);
  }

  return function connectHOC(WrappedComponent) {
    function ConnectFunction(props) {
      const { ...wrapperProps } = props;

      const contextValue = useContext(ReactReduxContext);

      const { store, subscription: parentSub } = contextValue;  // 解構出store和parentSub
      
      const actualChildProps = childPropsSelector(store, wrapperProps);

      const lastChildProps = useRef();
      useLayoutEffect(() => {
        lastChildProps.current = actualChildProps;
      }, [actualChildProps]);

      const [
        ,
        forceComponentUpdateDispatch
      ] = useReducer(storeStateUpdatesReducer, 0)

      // 建立一個subscription執行個體
      const subscription = new Subscription(store, parentSub);

      // state回調抽出來成為一個方法
      const checkForUpdates = () => {
        const newChildProps = childPropsSelector(store, wrapperProps);
        // 如果參數變了,記錄新的值到lastChildProps上
        // 并且強制更新目前元件
        if(!shallowEqual(newChildProps, lastChildProps.current)) {
          lastChildProps.current = newChildProps;

          // 需要一個API來強制更新目前元件
          forceComponentUpdateDispatch();

          // 然後通知子級更新
          subscription.notifyNestedSubs();
        }
      };

      // 使用subscription注冊回調
      subscription.onStateChange = checkForUpdates;
      subscription.trySubscribe();

      // 修改傳給子級的context
      // 将subscription替換為自己的
      const overriddenContextValue = {
        ...contextValue,
        subscription
      }

      // 渲染WrappedComponent
      // 再次使用ReactReduxContext包裹,傳入修改過的context
      return (
        <ReactReduxContext.Provider value={overriddenContextValue}>
          <WrappedComponent {...actualChildProps} />
        </ReactReduxContext.Provider>
      )
    }

    return ConnectFunction;
  }
}

export default connect;
           

到這裡我們的

React-Redux

就完成了,跑起來的效果跟官方的效果一樣,完整代碼已經上傳GitHub了:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux

下面我們再來總結下

React-Redux

的核心原理。

總結

  1. React-Redux

    是連接配接

    React

    Redux

    的庫,同時使用了

    React

    Redux

    的API。
  2. React-Redux

    主要是使用了

    React

    context api

    Redux

    store

  3. Provider

    的作用是接收

    Redux store

    并将它放到

    context

    上傳遞下去。
  4. connect

    的作用是從

    Redux store

    中選取需要的屬性傳遞給包裹的元件。
  5. connect

    會自己判斷是否需要更新,判斷的依據是需要的

    state

    是否已經變化了。
  6. connect

    在判斷是否變化的時候使用的是淺比較,也就是隻比較一層,是以在

    mapStateToProps

    mapDispatchToProps

    中不要反回多層嵌套的對象。
  7. 為了解決父元件和子元件各自獨立依賴

    Redux

    ,破壞了

    React

    父級->子級

    的更新流程,

    React-Redux

    Subscription

    類自己管理了一套通知流程。
  8. 隻有連接配接到

    Redux

    最頂級的元件才會直接注冊到

    Redux store

    ,其他子元件都會注冊到最近父元件的

    subscription

    執行個體上。
  9. 通知的時候從根元件開始依次通知自己的子元件,子元件接收到通知的時候,先更新自己再通知自己的子元件。

參考資料

官方文檔:https://react-redux.js.org/

GitHub源碼:https://github.com/reduxjs/react-redux/

文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝啬你的贊和GitHub小星星,你的支援是作者持續創作的動力。

歡迎關注我的公衆号進擊的大前端第一時間擷取高品質原創~

“前端進階知識”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd

“前端進階知識”系列文章源碼GitHub位址: https://github.com/dennis-jiang/Front-End-Knowledges

手寫一個React-Redux,玩轉React的Context API

繼續閱讀