React應用狀態(tài)管理(一):理解Action、Reducer逾一、Store

因為正在開發(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熄驼。
它提供的幾個主要方法:

以下是對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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末咸灿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侮叮,更是在濱河造成了極大的恐慌避矢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囊榜,死亡現(xiàn)場離奇詭異审胸,居然都是意外死亡,警方通過查閱死者的電腦和手機卸勺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門砂沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曙求,你說我怎么就攤上這事碍庵。” “怎么了悟狱?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵静浴,是天一觀的道長。 經(jīng)常有香客問我挤渐,道長马绝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任挣菲,我火速辦了婚禮富稻,結果婚禮上,老公的妹妹穿的比我還像新娘白胀。我一直安慰自己椭赋,他們只是感情好,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布或杠。 她就那樣靜靜地躺著哪怔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪向抢。 梳的紋絲不亂的頭發(fā)上认境,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音挟鸠,去河邊找鬼叉信。 笑死,一個胖子當著我的面吹牛艘希,可吹牛的內容都是我干的硼身。 我是一名探鬼主播硅急,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼佳遂!你這毒婦竟也來了营袜?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤丑罪,失蹤者是張志新(化名)和其女友劉穎荚板,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吩屹,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡啸驯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祟峦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罚斗。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宅楞,靈堂內的尸體忽然破棺而出针姿,到底是詐尸還是另有隱情,我是刑警寧澤厌衙,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布距淫,位于F島的核電站,受9級特大地震影響婶希,放射性物質發(fā)生泄漏榕暇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一喻杈、第九天 我趴在偏房一處隱蔽的房頂上張望彤枢。 院中可真熱鬧,春花似錦筒饰、人聲如沸缴啡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽业栅。三九已至,卻和暖如春谬晕,著一層夾襖步出監(jiān)牢的瞬間碘裕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工攒钳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帮孔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓夕玩,卻偏偏與公主長得像你弦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子燎孟,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內容