因為正在開發(fā)的是一個小項目铸本,除了登錄用戶信息、全局配置沒有其他需要集中管理的狀態(tài)嬉荆,就沒打算用Redux归敬。殺雞焉用牛刀,也有盡量少依賴第三方的強迫癥鄙早,不想整合redux汪茧,react-redux和redux-thunk三個新包。當然Redux有很多優(yōu)點:已經(jīng)很完善且生態(tài)強大限番、擁有清晰的規(guī)范體系和特定的設計約束舱污、以及精挑細選的API。遵循這種狀態(tài)管理方案的最佳實踐弥虐,在大型團隊項目中可以很好地幫助我們約定代碼規(guī)則扩灯,減少成員出錯和亂套的概率。Mobx則是面向對象思維的狀態(tài)管理庫霜瘪,我還沒有用過珠插。
這次我想試試靠React單打,一通百度有樣學樣用React Hook的useContext
結合useReducer
模擬了Redux的狀態(tài)管理機制颖对。實質就是一個可以處理同步/異步動作的函數(shù)捻撑,并根據(jù)動作函數(shù)更新狀態(tài)的集中狀態(tài)管理器。這邊記錄下摸索過程的心得體會缤底。
我決定又悄咪咪分個上下兩篇:占坑還沒寫完orz React應用狀態(tài)管理(二):用React Hook模擬實現(xiàn)Redux
參考文章地址:使用Hooks代替Redux顾患、React Hooks 版實現(xiàn) Redux
狀態(tài)管理三劍客:Action、Reducer个唧、Store(不局限于Redux)
1. Action
Action 就是一個普通JS對象江解,作用除了描述對 store 數(shù)據(jù)的更改操作,更是一個把數(shù)據(jù)從應用傳到store的可靠載體(數(shù)據(jù)有可能是服務器響應徙歼,用戶輸入等等)犁河。它必須包含一個type
字段表明要操作的類型鳖枕。
一般都是通過 store.dispatch()
把 action 傳到 store。
const ADD = 'ITEMS_ADD'
{ type: ADD, payload: 'text' }
在大型項目中桨螺,把type的值顯式地定義成常量會更利于團隊協(xié)作耕魄。
Action 只做簡單的參數(shù)傳遞和配置,最多做一些數(shù)據(jù)格式的轉換彭谁,如把 date 轉成 formatted string吸奴。盡量把數(shù)據(jù)處理邏輯放在reducer中。因為按照語意 action 就只是一個 identifier缠局,reducer 才是負責邏輯處理的则奥。
Action 創(chuàng)建函數(shù)是生成 action 的方法,這樣做更方便我們傳遞數(shù)據(jù)狭园。
就是像這樣簡單的返回一個 action:
export function completeItem(id: number) {
return {
type: ITEMS.COMPLETE,
payload: id
};
}
然后在項目中把 action 創(chuàng)建函數(shù)的結果傳給 dispatch() 方法即可發(fā)起一次 dispatch 過程读处。dispatch(completeItem(id))
或者創(chuàng)建一個 被綁定的 action 創(chuàng)建函數(shù) 來自動 dispatch,再直接調用:
const boundCompleteItem = id => dispatch(completeItem(id))
boundCompleteItem(id);
2. Reducer
reducer 是一個接收舊 state 和 action唱矛,并返回新的 state 的函數(shù)罚舱。它指定了如何響應 actions 并發(fā)送到 store 。記住 actions 只是描述了有事情發(fā)生了這一事實绎谦,并沒有描述應用如何更新 state管闷。
它與被傳入 Array.prototype.reduce(reducer, ?initialValue)
里的回調函數(shù)屬于相同的類型,這就是reducer命名的由來窃肠。
/** arr.reduce(callback, [initialValue]) **/
/* 第一個參數(shù)是回調函數(shù): (prev, curr) => prev + curr *
與redux當中的reducer模型 (previousState, action) => newState 是不是非常相似呢 */
var sum = [0,1,2,3,4].reduce((prev,curr) => prev + curr); // sum = 10
保持 reducer 純凈非常重要包个。永遠不要在 reducer 里調用非純函數(shù)或有執(zhí)行副作用的操作(如 API 請求和路由跳轉)。
不要直接修改 state
冤留。 可以用對象展開運算符的 { ...state, ...newState }
達到合并新舊數(shù)據(jù)并返回新指針對象的目的碧囊。
一個reducer函數(shù)示例
export const initialState = [];
// reducer接收 舊state和 action 并返回新的state
export default function items(state = initialState, action) {
// 根據(jù)不同的action.type對state進行不同的操作
switch (action.type) {
case ITEMS.RESET: {
return [];
}
case ITEMS.ADD: {
return [
...state,
{
id: Date.now(),
text: action.payload,
completed: false,
},
];
}
case ITEMS.COMPLETE: {
return state.map((item) => {
if (item.id === action.payload) {
return { ...item, completed: !item.completed }
}
return item;
});
}
default: {
return state;
}
}
}
3. Store
Store 只是有幾個方法的JS對象。它把Action和Reducer聯(lián)系到一起纤怒,用來維持應用所有的 state 樹 糯而。 要創(chuàng)建一個store,只需要把根部的 reducer 函數(shù)傳遞給 createStore
泊窘。 改變 store 內 state 的惟一途徑是對它 dispatch 一個 action熄驼。
它提供的幾個主要方法:
-
getState()
:獲取當前的 state; -
dispatch(action)
:分發(fā)action來更新 state州既; -
subscribe(listener)
:注冊監(jiān)聽器和注銷監(jiān)聽器谜洽。
以下是對createStore源碼的模擬:
const createStore = (reducer: any)=>{
let state: any;
let listeners:[] = [];
// 用來返回當前的state
const getState = () => state;
// 根據(jù)action調用reducer返回新的state并觸發(fā)listener
const dispatch=(action: any)=>{
state=reducer(state,action);
listeners.forEach((listener: any) => listener());
};
/* 這里的subscribe有兩個功能
* 調用 subscribe(listener) 會使用listeners.push(listener)注冊一個listener * 而調用 subscribe 的返回函數(shù)則會注銷掉listener */
const subscribe = (listener: never) => {
listeners.push(listener);
return () => {
listeners:[] = listeners.filter(l=>l!==listener);
};
};
return{ getState, dispatch, subscribe };
};
創(chuàng)建store
import rootReducer from './reducers'
let store = createStore(rootReducer)
最后貼一個用js簡單實現(xiàn)的基礎版redux萝映。
可以幫助我們捋清redux最基本的流程吴叶。異步代碼非原創(chuàng),出處: 淺談Redux Action & Reducer & Store
下篇接著講包含異步Action的React Hook版實現(xiàn)序臂。
const store = {
state: {}, // 全局唯一的state蚌卤,內部變量实束,通過getState()獲取
listeners: [], // listeners,用來諸如視圖更新的操作
dispatch: () => {}, // 分發(fā)action
subscribe: () => {}, // 用來訂閱state變化
getState: () => {}, // 獲取state
}
const createStore = (reducer, initialState) => {
// internal variables
const store = {};
store.state = initialState;
store.listeners = [];
// api-subscribe
store.subscribe = (listener) => {
store.listeners.push(listener);
return () => {
store.listener = store.listeners.filter(l => l !== listener);
};
};
// api-dispatch
// 根據(jù) action 調用reducer返回新的state逊彭,并觸發(fā)listener
store.dispatch = (action) => {
store.state = reducer(store.state, action);
store.listeners.forEach(listener => listener());
};
// api-getState
store.getState = () => store.state;
return store;
};
// reducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
let store = createStore(counter)
store.subscribe(() =>
console.log(store.getState())
)
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1