React | 在 Hooks 中以 useContext 與 useReducer 實現 Redux
前言
「目前剛在學習 React ,請問該選擇 Redux 或是直接學 Hooks 呢?」
這個問題是這幾天有個朋友提出來的,雖然不是很專業,但也就客觀的回覆一些看法,想知道的話可以直接滑到文章最後!
在此想表達的是在 Hooks 剛出現時,我也曾經思考過「 Redux 到底還有沒有用?」,雖然現在已經有了自己的一套看法,但始終沒有使用過 Hooks 實作過 Redux ,因此花一些時間實作,並讓這篇文章誕生。
Hooks
在 React 更新了後出現了許多 Hooks ,在實現 Redux 的過程中,最重要的兩個關鍵就是 useContext
及 useReducer
,下方會先解說它們的用法。
useContext
useContext
是用來接收與父 Component 傳遞資料時的 Hooks ,用途和 Props 相同,可以先看使用例子:
執行結果:
在使用 useContext
前會先在上層 Component 以 createContext
建立一個 Context Component ,並將要傳遞的資料放到 value
中,接著在下層 Component 中便能直接將 Context Component 傳給 useContext
取得 value
的資料。
這個做法解決了透過 Props 傳遞資料時常常會經過太多層,且讓不需要該資料的 Component 也都擁有的狀況。
useReducer
對使用過 Redux 的讀者來說, Reducer 應該都不陌生,當初看到時還以為是 React 本身要內建一個與 Redux 配合的 Hooks ,沒想到令人興奮的是,這個 Reducer 是在 React 中用 Hooks 申裝的新功能。
主要的功能其實和 Redux 中相同,是用來管理狀態和描述動作的,但使用上還是有些微不同:
執行結果:
由上方可看到與 Redux 明顯的差異是,初始資料 initState
不再是傳入 Reducer 中,而是在 useReducer
時一併處理。
實現 Redux
在實作的過程中,會以 createContext
建立的 Context Component代替 react-redux 的 Provider
,將所有的資料放在最上層,並在需要資料的 Component 中以 useContext
代替 mapPropsToState
和 connect
取得。
但可惜的是 Hooks 並沒有類似 combineReducer
的 Function ,能夠將所有的 Reducer 合併成一個,再交由 Provider
保管,因此必須得把這個關鍵的輪子造出來。
這裡另外說明一下為什麼沒有 combineReducer
就無法,因為 React 中的 useReducer
是在使用的時候才給初始 State ,這會導致每個 Reducer 管理 State 的 Container 都是分開的,但是如果是透過 combineReducer
將所有 Reducer 合併成一個,再在最上層使用 useReducer
產生一次 State 的 Container,並將此 Container 傳到下層的 Component 就確保每個 Component 都共用同一個 State 。
combineReducer
combineReducer
中主要的邏輯有兩個:
- 將所有的初始 State 合併成一個大 State 。
- 讓 Reducer 始終是透過指令,回傳一個處理過後的 State。
下方的是一個簡單的 combineReducer
實現,是去偷看 Redux 的 Github 所改寫的:
重點在於一開始檢測 State 時,將回傳的初始 State 存進 objInitState
裡面,接著是回傳一個 Reducer 模式的 Function ,同樣是傳入初始 State 及 action 動作,不同的點是會將指令帶入所有的 Reducer 中執行,再把執行後的新 State 放回 objInitState
中回傳。
實作 Redux
現在補足了 combineReducer
後,就能開始使用 Hooks 實現 Redux 。
先把上方範例中的 todoReducer
拆到另外的檔案中:
有另外做更改的是 initState
的部分,讓 initState
直接預設給 Reducer 。
接著要把 todoReducer
給 combineReducer
合併,雖然這個範例只有一個 Reducer ,但各位也可以直接使用多個 Reducer :
最後回到 Component 處理,為了讓大家有點 Redux 的感覺,因此下方會列出三個 Component ,分別為主要輸出的 Main
, createContext
也會放在他的外層,另外的 Todo
和 Head
分別為 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 樣子:
如果在 combineReducer
合併時如下方:
const reducers = combineReducer({
todo: todoReducer,
test: testReducer,
})
那 State 就會是這樣子的物件:
{
todo: {...},
test: {...},
}
Head
的話用來顯示目前的待辦事項數,確認是否與 Todo
共用相同 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 間的看法!文章中如果有任何問題或是需要改進的地方,也麻煩不吝指教!謝謝!
參考文章
- https://github.com/reduxjs/redux/blob/master/src/combineReducers.js
- https://blog.csdn.net/qq_26708777/article/details/79304216
- https://medium.com/@nightspirit622/npm-uninstall-redux-c00d86683b0d?fbclid=IwAR05ICi8I5_tnGVqjzhIn7tHxilFu0raIk_DNKfiMfyM--StuZ06GjVm5YI
- https://reactjs.org/docs/hooks-reference.html#usereducer