針對狀態管理,前面有看過最基本的useState,也有看過useReducer用法,甚至還有搭配useContext,讓state可以不用再層層傳遞,但是目前為止state都只能直系傳遞,也就是只能傳遞給自己層級以下的元件,如果是兩個放在同一層的元件,就無法互相傳遞狀態。但總還是會有一些情境,會需要全域傳遞,這時候前面提到過的hooks都無法達到這個目的,只能另外透過其他Libray達到這個目的,今天要看的也就是可以於全域環境下傳遞狀態的狀態管理Libray。
在Vue的世界裡,講到管理全域狀態的工具大家一定都會想到Vuex,在Vue3之後,官方主推的是另外一套叫做Pinia的Libray,所以在透過CLI創建專案時,會是問要不要使用Pinia。雖然它與Vuex只有寫法上有差異,不過整體概念其實一樣。而到了React的世界,想要全域管理狀態則是使用Redux。這裡要特別強調一下,Redux雖然跟React名字很相像,但Redux並不只限於使用在React上,相反的,Vuex則是專門為了Vue所設計的狀態管理工具。
接著來了解一下Vuex和Redux對於資料操作的整體流程是什麼?
在Vuex的狀態管理機制下,會統一將state保管在store裡面,如果有非同步的請求時,會先dispatch一個action,再透過commit mutaion來更新state。如果是同步的動作,則可以直接透過commit一個mutaion來更新state。另外,雖然需要更新state時,可以直接對state進行改動,不過為了讓改動比較好追蹤,還是建議一律使用mutation的方式更新state,不過這樣還是在mutable特性下的改動,只會被包裝成mutation函式,且必須透過commit來觸發。
在Redux的狀態管理機制下,一樣會將state管理在store裡,並且會透過reducer定義變更state的動作有哪些,如果要更新state時,會dispatch一個action,再依照reducer定義的action type決定要進行什麼內容的state的更新。這裡的reducer與前面也提到的useReducer接收action來更新state的部分類似。
前面快速看了Vuex和React對於state操作整體流程後,其中有幾個在流程中很像角色和動作,這裡也來稍微做個對比。
Vuex和Redux的store都是用來集中管理state和state相關操作的地方,也可以用來進行模組化的管理。例如和account相關的state和更新、操作state的動作都可以管理在accountStore。
Vuex會在state區塊中定義Vuex管理的state;Redux則是透過把初始state傳入reducer,來帶入來定義全域的state。除此之外,還會透過reducer定義不同的action type來告知Redux要怎麼更新state。
在Vuex中,透過state區塊定義要管理的state
const store = createStore({
state () {
return {
count: 0
}
}
})
在Redux中,會先透過把初始值傳入reducer來定義要管理的state,以及定義更新state的action type。
const initialState = {
count: 0,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_COUNT':
return {
...state,
count: state.count + 1,
};
default:
return state;
}
};
const store = createStore(reducer);
在Vuex裡,mutation函式是用來更新state的函式,mutation函式會被定義在mutations的區塊,並且透過commit觸發;如果是Redux的話,會把更新state的動作也定義在Reducer裡面,要更新state時,則會透過dispatch一個action來讓reducer根據action type進行相對應的state的更新。
Vuex會這樣把更新state的函式都管理在mutation的區塊。
const store = createStore({
state () {
// 略...
},
mutations: {
addCount (state) {
state.count++
}
}
})
// 使用時
commit('addCount')
Redux則是透過Reducer定義更新state的action type。
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_COUNT':
return {
...state,
count: state.count + 1,
};
case 'MINUS_COUNT':
return {
...state,
count: state.count - 1,
};
default:
return state;
}
};
// 創建store後,就可以dispatch action
store.dispatch({ type: 'ADD_COUNT' });
在Vuex裡的action是一個函式,主要是用來處理非同步的操作,且會被定義在actions的區塊。在使用時,是透過dispatch來觸發;Redux的action是一個物件,通常包含type的這個屬性
。在實務中,通常會透過action creator回傳action,這個action會用來描述要執行什麼樣的state操作。實際使用時,則會用dispatch把action發送到reducer中,進行state的操作。
Vuex會把action
const store = createStore({
state () {
// 略...
},
mutations: {
// 略...
},
actions: {
addCountAction(context) {
// 在action中,可以透過context這個參取得commit,這樣就可以將更新值更新到store裡面
context.commit('addCount');
}
},
})
Redux會透過action creator的函式回傳一個action物件,再透過dispatch傳到reducer裡面更新state。
// action creator回傳action
const addCount = () => {
return {
type: 'ADD_COUNT',
};
};
// 實際使用時
store.dispatch(addCount());
Vuex可以透過getter來取得的一些處理過的state,這些處理過的state會被放在getters區塊;Redux本身沒有getter的這個用法,但是可以透過自己建立selector函式來讓實作時可以快速取得要的state,如果搭配React-Redux使用,則可以用useSelector這個hook來取得state。
Vuex
const store = createStore({
state () {
// 略...
},
mutations: {
// 略...
},
getters: {
// 取得總金額的getter
totalPrice: (state) => {
return state.cart.reduce((total, product) => {
return total + product.price * product.quantity;
}, 0);
}
}
})
Redux
const { cartItemList } = useSelector(state => state.shoppingCartReducer);
Vuex要改動store裡的state時,會透過commit觸發mutation;如果Redux要改動store的state時,則是透過dispatch帶入action。
Vuex
store.commit('addCount');
Redux
dispatch({ type: "ADD_COUNT" });
Vuex會把處理非同步的操作放在action區塊管理,並用dispatch觸發action;雖然Redux也可以透過發送action來處理非同步的操作,但如果手動處理非同步操作的話,需要自己額外去管理非同步動作的開始、結束,以及錯誤的狀態,所以通常會透過Redux Thunk或Redux Sage這些middleware套件來處理,讓這些非同步操作的狀態可以被管理在一起。如果是使用React Toolkit,也可以用更便利的方式,也就是React Toolkit提供的RTK Query來處理非同步的操作。這部分也會在後面實作的時候,再進一步地了解。
今天快速地從Vuex延伸到Redux,以及從比較Vuex和Redux flow中的一些角色和動作,來進一步熟悉它們管理及更新store內state的流程,明天會接著透過實作的方式,來熟悉在React中如何使用Redux。