React | 在 Hooks 中以 useContext 與 useReducer 實現 Redux

神Q超人
Enjoy life enjoy coding
9 min readMar 24, 2019
用 useContext 與 useReducer 實現 Redux

前言

「目前剛在學習 React ,請問該選擇 Redux 或是直接學 Hooks 呢?」

這個問題是這幾天有個朋友提出來的,雖然不是很專業,但也就客觀的回覆一些看法,想知道的話可以直接滑到文章最後!

在此想表達的是在 Hooks 剛出現時,我也曾經思考過「 Redux 到底還有沒有用?」,雖然現在已經有了自己的一套看法,但始終沒有使用過 Hooks 實作過 Redux ,因此花一些時間實作,並讓這篇文章誕生。

Hooks

在 React 更新了後出現了許多 Hooks ,在實現 Redux 的過程中,最重要的兩個關鍵就是 useContextuseReducer ,下方會先解說它們的用法。

useContext

useContext 是用來接收與父 Component 傳遞資料時的 Hooks ,用途和 Props 相同,可以先看使用例子:

useContext 的使用方式

執行結果:

在使用 useContext 前會先在上層 Component 以 createContext 建立一個 Context Component ,並將要傳遞的資料放到 value 中,接著在下層 Component 中便能直接將 Context Component 傳給 useContext 取得 value 的資料。

這個做法解決了透過 Props 傳遞資料時常常會經過太多層,且讓不需要該資料的 Component 也都擁有的狀況。

useReducer

對使用過 Redux 的讀者來說, Reducer 應該都不陌生,當初看到時還以為是 React 本身要內建一個與 Redux 配合的 Hooks ,沒想到令人興奮的是,這個 Reducer 是在 React 中用 Hooks 申裝的新功能。

主要的功能其實和 Redux 中相同,是用來管理狀態和描述動作的,但使用上還是有些微不同:

執行結果:

透過 dispatch 呼叫 Reducer 改變 state ,最後 render 畫面

由上方可看到與 Redux 明顯的差異是,初始資料 initState 不再是傳入 Reducer 中,而是在 useReducer 時一併處理。

實現 Redux

在實作的過程中,會以 createContext 建立的 Context Component代替 react-redux 的 Provider ,將所有的資料放在最上層,並在需要資料的 Component 中以 useContext 代替 mapPropsToStateconnect 取得。

但可惜的是 Hooks 並沒有類似 combineReducer 的 Function ,能夠將所有的 Reducer 合併成一個,再交由 Provider 保管,因此必須得把這個關鍵的輪子造出來。

這裡另外說明一下為什麼沒有 combineReducer 就無法,因為 React 中的 useReducer 是在使用的時候才給初始 State ,這會導致每個 Reducer 管理 State 的 Container 都是分開的,但是如果是透過 combineReducer 將所有 Reducer 合併成一個,再在最上層使用 useReducer 產生一次 State 的 Container,並將此 Container 傳到下層的 Component 就確保每個 Component 都共用同一個 State 。

combineReducer

combineReducer 中主要的邏輯有兩個:

  1. 將所有的初始 State 合併成一個大 State 。
  2. 讓 Reducer 始終是透過指令,回傳一個處理過後的 State。

下方的是一個簡單的 combineReducer 實現,是去偷看 Redux 的 Github 所改寫的:

重點在於一開始檢測 State 時,將回傳的初始 State 存進 objInitState 裡面,接著是回傳一個 Reducer 模式的 Function ,同樣是傳入初始 State 及 action 動作,不同的點是會將指令帶入所有的 Reducer 中執行,再把執行後的新 State 放回 objInitState 中回傳。

實作 Redux

現在補足了 combineReducer 後,就能開始使用 Hooks 實現 Redux 。

先把上方範例中的 todoReducer 拆到另外的檔案中:

有另外做更改的是 initState 的部分,讓 initState 直接預設給 Reducer 。

接著要把 todoReducercombineReducer 合併,雖然這個範例只有一個 Reducer ,但各位也可以直接使用多個 Reducer :

最後回到 Component 處理,為了讓大家有點 Redux 的感覺,因此下方會列出三個 Component ,分別為主要輸出的 MaincreateContext 也會放在他的外層,另外的 TodoHead 分別為 Main 的下層 Component ,那從 Main 開始處理:

需要注意的是,因為每個 Component 都分開寫,所以要特別匯出 Context Component ,讓子 Component 可以使用,另外透過 Provider 傳下去的是使用 useReducer 創建後的 State 了,因此 State 只會由這裡流入各個 Component。

接下來是用 useContext 取代 Redux 中的 mapStateToProps

在下層 Component 中需要注意的是, State 為所有 Reducer 合併後的資料,因此在使用上得多一層 state.todo.list ,先指定到 todo 再取得 list 的資料,以下為 combineReducer 後的 State 樣子:

所有 Reducer 的 State 經過 combineReducer 後都會變成一個大 State

如果在 combineReducer 合併時如下方:

const reducers = combineReducer({
todo: todoReducer,
test: testReducer,
})

那 State 就會是這樣子的物件:

{
todo: {...},
test: {...},
}

Head 的話用來顯示目前的待辦事項數,確認是否與 Todo 共用相同 State :

執行結果如下:

Todo 及 Head 共享了 Main 在 Context Component 中流入的 Reducer State

雖然還是有一些小缺點,例如每次透過 useContext 拿到的 State 就是一整包,還要特別再以 Key 取得對應的 Reducer ,才能取得 State 就滿麻煩的,但總結來說還是完成了用 Hooks 實現了 Redux 的主要功能。

最後分享一下,到底是要選擇 Redux 或是直接學習 Hooks 這個問題。

先單就 Redux 來說,它負責為整個 Web App 管理 State 的套件,使用上也常需要其他第三放套件解決一些問題(例如: react - thunk 或 redux - saga 處理異步請求),但反過來說也表示 Redux 的生態系很豐富,也許學習曲線會稍微高一些,不過在熟悉使用後會有點愛不釋手的感覺,且現在使用 React 的公司大多都會搭配 Redux 開發,雖然筆者的公司是用 dva ,但它也是 Redux 包裝來的!

另外就是 Hook 的誕生,無庸置疑 Hook 改善了許多 React 的開發環境,包括在 Function Component 中也能夠有自己的 State,更出現了 useReducer 這種神似 Redux 的 Hook ,如果再搭配 useContext 還原度就至少有 87% ,就像文章中實現的方式,不過還是會遇到需要使用 Promise 處理異步請求的問題,只是和 Redux 相比起來,整個專案也會變得乾淨多了,但這也代表許多輪子都要自己造。

結論可以直接看下來!

筆者認為雖然 Hook 誕生了,擁有自我實現類似 Redux 的功能,但是 React 團隊表示 Hook 的出現並不是 Redux 的末日,而是希望 React 能夠「不是必須需要第三方套件」,也能夠自己做到基本的事情!而且 Redux 和 Hook 同時使用也不衝突,所以會建議別把它們當作分支技能,兩者都學的幫助會更大,如果不知道先學哪一個,就 Redux 先吧!畢竟還是得考慮市場的主流趨勢!

最後結論的團隊表示出處:https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet/

可直接看 “ Redux will be replaced by React’s new Hooks API ” 這個部分。

以上是個人想法,歡迎各位留言討論對 Hooks 與 Redux 間的看法!文章中如果有任何問題或是需要改進的地方,也麻煩不吝指教!謝謝!

參考文章

  1. https://github.com/reduxjs/redux/blob/master/src/combineReducers.js
  2. https://blog.csdn.net/qq_26708777/article/details/79304216
  3. https://medium.com/@nightspirit622/npm-uninstall-redux-c00d86683b0d?fbclid=IwAR05ICi8I5_tnGVqjzhIn7tHxilFu0raIk_DNKfiMfyM--StuZ06GjVm5YI
  4. https://reactjs.org/docs/hooks-reference.html#usereducer

--

--