做了好多次練習。
簡述
這邊會跟著 Redux 官方文件 來跑一次流程。
在介紹之前,讓我在強調一次:
- Redux 並沒有跟 React 綁在一起
- Redux 並沒有跟 React 綁在一起
- Redux 並沒有跟 React 綁在一起
我在學之前也以為他是專屬於 React 的東西,畢竟是以「Re」開頭的嘛。
總之呢,它只是一個基於 flux 打造的 library,用來「管理狀態」。你可以搭配 Vanilla JS 來用,或甚至是別的程式語言也可以。
從四大要素開始
這邊先簡單介紹 Redux 裡面幾個主要的角色:
- store(透過 reducer 來建立)
- reducer(跟 array 的 reduce 概念很相似)
- action(一個 Object,會有 type 跟 payload)
- dispatch(透過它來發出 action 給 reducer)
等一下的範例會一一介紹它們是幹嘛用的,廢話不多說,開始吧!
這邊的範例很簡單,只需要寫一隻檔案就行了,所以直接附上 code:
1 | import { createStore } from 'redux'; |
要建立一個 store 的第一步是先寫好「reducer」,這個範例的 reducer 是 todosReducer
這個 function。
簡單來說,reducer 就是一個用來「產生 state」的東西,只要給他對應的 「action」,它就吐給你對應的 state。
至於 reducer 裡面要做什麼處理是我們自己決定的,像是我們希望接收到 addTodo
這個 action 時,就新增一筆 todo 到 state 裡面,所以才會有這段:
1 | switch (action.type) { |
後面的 deleteTodo
也是以此類推。
定義好 reducer 以後,只要把它丟到 Redux 提供的 createStore
,store 就建立好了,就是這麼簡單。
1 | const store = createStore(todosReducer); |
接下來,每當我想要對 store 裡面的東西做事情,就得透過「dispatch」+「action」才可以:
1 | store.dispatch({ |
這一段的意思就是說「我想執行 addTodo
這個 action」,麻煩幫我 dispatch(指派)給 reducer。
眼尖一點就會注意到 action 其實只是一個 Object,裡面會放 type
跟 payload
這兩個 key,代表我想做的事情跟額外資訊。
所以當我 dispatch 這個 action 以後,reducer 就會吐給我新的 state,它應該要長的像這樣:
1 | { |
以上就是最基本的流程,沒有很複雜,本質就是這樣而已。
至於這一段:
1 | store.subscribe(() => { |
其實就跟 addEventLisener()
87 分像,它的意思是「當 state 改變的時候幫我 call 這個 function」:
1 | button.addEventListender(() => ...); |
就這樣而已。
最後是做個補充,當我們在 Reducer 裡面更新 state 時,一定要用 Immutable 的方式來改變,這邊先示範一個錯誤的範例:
1 | const initState = { |
這樣子更新後的 state 就會變成:
1 | { |
為什麼?我說過一定要用 Immutable 的方式來改變 state,而剛剛在 reducer 裡面回傳的只有 todos
,所以 email
就消失了。
正確的做法應該是這樣子:
1 | const initState = { |
這就跟在用 useState
的概念是一樣的,不要忘記囉!
來做點優化,加上 action type 與 action creator
前面雖然已經介紹過 Redux 的基本用法,不過應該能注意到幾個小問題:
- action 是用「純字串」來寫的,那打錯字怎麼辦?
- 每次 dispatch 都要傳一包 Object 是不是有點太 hard code 了?
Action Type
首先是第一個問題,這其實蠻困擾的,因為假設我哪天打錯字的話:
1 | store.dispatch({ |
這樣是不會出跳出任何錯誤的,因為對 reducer 而言 addTodos
只是一個不存在的 case
,所以只會跳到 default
區塊而已。但這樣麻煩可就大了,因為你可能根本不知道是自己打錯字的關係。
所以更好的做法是改用「Action Type」,其實就只是建立一個 constant 啦:
1 | const actionTypes = { |
接著就可以去把原本的 action 修改成這樣:
1 | // 1. reducer 裡的 action |
現在把 action 都改成變數以後,如果我又打錯字,系統就會直接噴 Error 跟我說Action may not have a undefined type property
之類的,不會再有原本那種找不出 bug 的麻煩。
Action creator
接著是第二個問題,如果每次 dispatch 都要這樣寫的話真的蠻麻煩的:
1 | store.dispatch({ |
如果可以寫成這樣的話世界是不是會更好?
1 | store.dispatch(addTodo('todo1')); |
有辦法做到嗎?其實剛剛有暗示過,你只要想想 action 的本質,就會發現要做到這點並不困難。
剛剛說過,action 的本質只是一個 Object,所以我們只要寫一個可以回傳對應 Object 的 function 不就好了嗎?
1 | function addTodo(name) { |
這種做法就叫做「Action creator」,講白話一點就是把 action 要傳的內容改用 function 來寫而已。
總之呢,只要改用這種方式優化後,你的 code 就會乾淨許多,也比較好維護。
最後應證一下我最開始說的,Redux 並沒有一定要跟 React 綁在一起用,所以這邊附上一個用 Vanilla JS + Redux 寫的 todo 範例,如果有任何疑惑就去看看吧。