1. 程式人生 > >手寫一個React-Redux,玩轉React的Context API

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

[上一篇文章我們手寫了一個Redux](https://juejin.im/post/5efec81be51d4534942dd589),但是單純的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](https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux)** ## 基本用法 下面這個簡單的例子是一個計數器,跑起來效果如下: ![Jul-02-2020 16-44-04](https://user-gold-cdn.xitu.io/2020/7/8/1732dd26deccaa02?w=338&h=112&f=gif&s=62209) 要實現這個功能,首先我們要在專案裡面新增`react-redux`庫,然後用它提供的`Provider`包裹整個`React`App的根元件: ```javascript import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux' import store from './store' import App from './App'; ReactDOM.render( , document.getElementById('root') ); ``` 上面程式碼可以看到我們還給`Provider`提供了一個引數`store`,這個引數就是Redux的`createStore`生成的`store`,我們需要調一下這個方法,然後將返回的`store`傳進去: ```javascript import { createStore } from 'redux'; import reducer from './reducer'; let store = createStore(reducer); export default store; ``` 上面程式碼中`createStore`的引數是一個`reducer`,所以我們還要寫個`reducer`: ```javascript 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`是`0`,同時他還能處理三個`action`,這三個`action`對應的是UI上的三個按鈕,可以對`state`裡面的計數進行加減和重置。到這裡其實我們`React-Redux`的接入和`Redux`資料的組織其實已經完成了,後面如果要用`Redux`裡面的資料的話,只需要用`connect`API將對應的`state`和方法連線到元件裡面就行了,比如我們的計數器元件需要`count`這個狀態和加一,減一,重置這三個`action`,我們用`connect`將它連線進去就是這樣: ```javascript 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 ( <>

Count: {count}

); } 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 ```javascript // 我們使用一個單獨的檔案來呼叫createContext // 因為這個返回值會被Provider和Consumer在不同的地方引用 import React from 'react'; const TestContext = React.createContext(); export default TestContext; ``` ### 使用`Context.Provider`包裹根元件 建立好了context,如果我們要傳遞變數給某些元件,我們需要在他們的根元件上加上`TestContext.Provider`,然後將變數作為`value`引數傳給`TestContext.Provider`: ```javascript import TestContext from './TestContext'; const setting = { color: '#d89151' } ReactDOM.render( , document.getElementById('root') ); ``` ### 使用`Context.Consumer`接收引數 上面我們使用`Context.Provider`將引數傳遞進去了,這樣被`Context.Provider`包裹的所有子元件都可以拿到這個變數,只是拿這個變數的時候需要使用`Context.Consumer`包裹,比如我們前面的`Counter`元件就可以拿到這個顏色了,只需要將它返回的`JSX`用`Context.Consumer`包裹一下就行: ```javascript // 注意要引入同一個Context import TestContext from './TestContext'; // ... 中間省略n行程式碼 ... // 返回的JSX用Context.Consumer包裹起來 // 注意Context.Consumer裡面是一個方法,這個方法就可以訪問到context引數 // 這裡的context也就是前面Provider傳進來的setting,我們可以拿到上面的color變數 return ( ); ``` 上面程式碼我們通過`context`傳遞了一個全域性配置,可以看到我們文字顏色已經變了: ![image-20200703171322676](https://user-gold-cdn.xitu.io/2020/7/8/1732dd2e8ea5f4c7?w=438&h=200&f=png&s=14000) ### 使用`useContext`接收引數 除了上面的`Context.Consumer`可以用來接收`context`引數,新版React還有`useContext`這個hook可以接收context引數,使用起來更簡單,比如上面程式碼可以這樣寫: ```javascript const context = useContext(TestContext); return ( <>

Count: {count}

      ); ``` 所以我們完全可以用`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`: ```javascript // Context.js import React from 'react'; const ReactReduxContext = React.createContext(); export default ReactReduxContext; ``` 這個檔案很簡單,新建一個`context`再匯出就行了,[對應的原始碼看這裡](https://github.com/reduxjs/react-redux/blob/master/src/components/Context.js)。 然後將這個`context`應用到我們的`Provider`元件裡面: ```javascript import React from 'react'; import ReactReduxContext from './Context'; function Provider(props) { const {store, children} = props; // 這是要傳遞的context const contextValue = { store }; // 返回ReactReduxContext包裹的元件,傳入contextValue // 裡面的內容就直接是children,我們不動他 return ( ) } ``` `Provider`的元件程式碼也不難,直接將傳進來的`store`放到`context`上,然後直接渲染`children`就行,[對應的原始碼看這裡](https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.js)。 ## 手寫`connect` ### 基本功能 其實`connect`才是React-Redux中最難的部分,裡面功能複雜,考慮的因素很多,想要把它搞明白我們需要一層一層的來看,首先我們實現一個只具有基本功能的`connect`。 ```javascript 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 } 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`將上次渲染的引數記錄下來: ```javascript // 記錄上次渲染引數 const lastChildProps = useRef(); useLayoutEffect(() => { lastChildProps.current = actualChildProps; }, []); ``` 注意`lastChildProps.current`是在第一次渲染結束後賦值,而且需要使用`useLayoutEffect`來保證渲染後立即同步執行。 因為我們檢測引數變化是需要重新計算`actualChildProps`,計算的邏輯其實都是一樣的,我們將這塊計算邏輯抽出來,成為一個單獨的方法`childPropsSelector`: ```javascript 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`返回了好幾層結構,比如這樣: ```json { stateA: { value: 1 } } ``` 你去改了`stateA.value`是不會觸發重新渲染的,`React-Redux`這樣設計我想是出於效能考慮,如果是深比較,比如遞迴去比較,比較浪費效能,而且如果有迴圈引用還可能造成死迴圈。採用淺比較就需要使用者遵循這種正規化,不要傳入多層結構,[這點在官方文件中也有說明](https://react-redux.js.org/using-react-redux/connect-mapstate#return)。我們這裡直接抄一個它的淺比較: ```javascript // 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 } ``` 在回撥裡面檢測引數變化: ```javascript // 註冊回撥 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`: ```javascript 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](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`類: ```javascript 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`對應的原始碼看這裡](https://github.com/reduxjs/react-redux/blob/master/src/utils/Subscription.js)。 ### 改造`Provider` 然後在我們前面自己實現的`React-Redux`裡面,我們的根元件始終是`Provider`,所以`Provider`需要例項化一個`Subscription`並放到`context`上,而且每次`state`更新的時候需要手動呼叫子元件回撥,程式碼改造如下: ```javascript 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 ( ) } export default Provider; ``` ### 改造`connect` 有了`Subscription`類,`connect`就不能直接註冊到`store`了,而是應該註冊到父級`subscription`上,更新的時候除了更新自己還要通知子元件更新。在渲染包裹的元件時,也不能直接渲染了,而是應該再次使用`Context.Provider`包裹下,傳入修改過的`contextValue`,這個`contextValue`裡面的`subscription`應該替換為自己的。改造後代碼如下: ```javascript 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 ( ) } return ConnectFunction; } } export default connect; ``` 到這裡我們的`React-Redux`就完成了,跑起來的效果跟官方的效果一樣,完整程式碼已經上傳GitHub了:[https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/react-redux](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/](https://react-redux.js.org/) GitHub原始碼:[https://github.com/reduxjs/react-redux/](https://github.com/reduxjs/react-redux/) **文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支援是作者持續創作的動力。** **作者博文GitHub專案地址: [https://github.com/dennis-jiang/Front-End-Knowledges](https://github.com/dennis-jiang/Front-End-Knowledges)** **作者掘金文章彙總:[https://juejin.im/post/5e3ffc85518825494e2772fd](https://juejin.im/post/5e3ffc85518825494e2