本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。
集中管理App的state。
React 的組件構成主要是樹狀架構,並且State主要是由親傳子的方向流動,
所以當某個Componet需要更新的State落在另一棵樹上的時候,麻煩就來了。
一般的處理是用提升State的作法,把該State拉升到兩棵樹的共用根源,然侯在從那兒下傳到兩個需要該State的Component上。
以下圖為例,若需要用B來更新某個在A上的State,就把那個State改寫在根源組件上,回傳state與setState方法給A、B兩部件使用。
當這種情形一多,你的根組件就會長滿一堆state與method。而且往下傳遞props時會路過許多不需要這些props的組件,多很多累贅的props敘述。
React有提供一些方法可以減輕這種狀況的,像是前面介紹的composition(跳過子組件傳遞props給孫組件),或是使用Context。
另外就是使用像Redux這類套件,把Store的管理從樹狀構造中獨立出來,集中管理,並讓每個component從這個獨立store取用需要的state跟更新方法。
要創建Redux store需要三元素:
states
專案的唯一資訊源。
actions
給store的指令,讓store根據action執行相應的動作。
先看action的範例:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
一個action就是個純javacript物件,而action慣例需要一個 type,幫助reducer辨識action後決定如何更新state。
action type建議使用固定字串,方便管理。
除了type外action可以包含額外的資訊(範例的payload),像是使用者輸入的字串等等,這部分命名、資料種類不拘。
一般不會把action寫死,而是用action creator根據輸入產生需要的action:
const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
reducers
左手是目前的state,右手是action,雙掌合十鍊成新的state。
白話: 接收action,根據目前的state產出新的state。
範例:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
在定義reducer的時候可以賦予state初始值,沒有的話state就是undefined。
每當store 收到action的時候,就會轉送給reducer,後者可以用任何方式回傳新的state,必須回傳。
一般是根據action的type用switch決定如何回傳新的state:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
注意Redux觸發畫面更新的機制與React相同: 比對新舊state差異,所以reducer中不該直接對state做變更(immutable),而是拷貝目前state值後對拷貝的物件作變更後回傳。
建立Redux架構的流程大致如下:
建立reducer,當中就包含對應的action、以及當沒有action時回傳的初始state。
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
建立好reducer後,用於定義store:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
用configureStore初始化store設定,會在沒有指定action的狀態下將reducer物件跑一遍後收到的default return作為state初始值。
創建好之後可以用store.getState()取得整個存在store中的state物件。
store建立好後就能用store.dispatch()執行想要的action。
const IncreamentAction = { type: 'counter/increment' }
store.dispatch(IncreamentAction)
以上是一步步定義的步驟,顯得有些瑣碎,不過實務上用redux toolkit建立splice集中管理action、reducer的關聯,會更易理解與管理,這部分在接下來的實作會操作。