用redux管理數據狀態

用redux管理數據狀態

動機

應用模塊之間須要訪問共享數據,採用redux管理數據狀態。全部數據保存在store tree中,用於維護數據狀態。
「Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。」html

目錄

  • 三大原則react

  • combineReducersgit

  • createStoregithub

  • store方法redux

  • 數據流api

  • middlewares數組

  • 實現redo&undo閉包

三大原則

單一數據源

整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。app

State 是隻讀的

唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。經過 store.dispatch() 將 action 傳到 store。action 內必須使用一個字符串類型的 type 字段來表示將要執行的動做異步

let action = {
    type: 'CHANGE_TEXT',
    text:'helloworld'
};
store.dispatch(action);

能夠經過Action 建立函數 生成 action 。

function changeText(text) {
  return {
    type: 'CHANGE_TEXT',
    text
  }
}
store.dispatch(changeText('helloworld'));

使用純函數來執行修改

經過reducer改變state tree,要求reducer是純函數,即只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。

function todoApp(state = initialState, action) {
  switch (action.type) {
    case 'CHANGE_TEXT':
      return Object.assign({}, state, {
        text: action.text
      })
    default:
      return state
  }
}

注意:
1)不要修改 state。
2)在 default 狀況下返回舊的 state。

combineReducers

開發一個函數來作爲主 reducer,它調用多個子 reducer 分別處理 state 中的一部分數據,而後再把這些數據合成一個大的單一對象。每一個 reducer 只負責管理全局 state 中它負責的一部分。每一個 reducer 的 state 參數都不一樣,分別對應它管理的那部分 state 數據。

reducers.js
import { combineReducers } from 'redux'
const initialUser = {
 name:"chen",
 age:10
};
const initialJob = {
 position:"engineer"
};
const userReducer = (state = initialUser, action) => {
 let payload = action.payload;
 let type = action.type;
 switch (type) {
   case "CHANGE_NAME":
     state = Object.assign({},state,{ name : payload});
     break;
   case "ADD_AGE":
     state = Object.assign({},state,{ age : state.age+1});
     break;
   default:
     break;
 }
 return state;
};
const jobReducer = (state = initialJob, action) => {
 let payload = action.payload;
 let type = action.type;
 switch (type) {
   case "CHANGE_POSITION":
     state = Object.assign({},state,{ position : payload});
     break;
   default:
     break;
 }
 return state;
};

const reducers = combineReducers({
 userReducer,
 jobReducer
})

export default reducers

createStore

經過createStore生成store tree。createStore() 的第二個參數是可選的, 用於設置 state 初始狀態

const store = createStore(
  reducers
);

store方法

提供 getState() 方法獲取 state;
提供 dispatch(action) 方法更新 state;
經過 subscribe(listener) 註冊監聽器;
經過 subscribe(listener) 返回的函數註銷監聽器。

數據流

嚴格的單向數據流
數據流動方向

clipboard.png

問題:沒法進行異步或輔助操做。

middlewares

提供了位於 action 被髮起以後,到達 reducer 以前的擴展點。 能夠用來進行日誌記錄、建立崩潰報告、調用異步接口或者路由。

如下是3個不一樣功能的中間件,經過輸出數組的方式將3箇中間件輸出模塊

middleware 機制

redux 提供了 applyMiddleware 這個 api 來加載 middleware

applyMiddleware.js源碼
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
middlewares
const m1 = store => next => action => {
  let startState = store.getState();
  console.log("m1 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m1 end");
  console.log(endState.user.age);
};
const m2 = store => next => action => {
  let startState = store.getState();
  console.log("m2 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m2 end");
  console.log(endState.user.age);
};
const m3 = store => next => action => {
  let startState = store.getState();
  console.log("m3 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m3 end");
  console.log(endState.user.age);
};

const middlewares = [m1, m2, m3];

export default middlewares
index.js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import middlewares from './middlewares';

import reducers from './reducers';
import middlewares from './middlewares';

const defaultState = {};
const store = applyMiddleware(...middlewares)(createStore)(reducers, defaultState);
window.store = store;

applyMiddleware 是一個多層柯里化(curry)的函數
經過applyMiddleware(...middlewares)能夠將[m1,m2,m3]3箇中間件串聯起來,下面分析如下具體實現方式。
1)初始化store將dispatch指向store.dispatch

const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch

2)用middlewareAPI封裝store方法,middlewareAPI.dispatch方法最終指向store.dispatch。

let chain = [] 
const middlewareAPI = {
   getState: store.getState,
   dispatch: (action) => dispatch(action)
}

clipboard.png

3)經過將middlewareAPI傳入每一箇中間件[m1,m2,m3],返回chain,此時chain=[f1,f2,f3],且因爲閉包,f1,f2,f3中的store都指向middlewareAPI ,最終指向store。這樣的好處就是每一個f都能訪問到同一個store。

chain = middlewares.map(middleware => middleware(middlewareAPI))

clipboard.png
4)經過compose函數將[f1,f2,f3] 串聯執行,最後dispatch被改寫成了以下函數。

dispatch = f1(f2(f3(store.dispatch)));
/*
dispatch = (action)=>{
  console.log("m1 start");
  ((action) =>{
    console.log("m2 start");
    ((action) => {
      console.log("m3 start");
      store.dispatch(action)
      console.log("m3 end");
    })(action);
    console.log("m2 end");
  })(action);
  console.log("m1 end");
}
*/

clipboard.png

能夠看到,此時f1’,f2’,f3’ 中的next指向中,只有最後一個f3’是指向store.dispatch,其他next指向前一個f'的輸出。f1’,f2’,f3’全部store都指向middlewareAPI,最終getState和dispatch仍是指向store。經過這樣的方式能夠將中間件串聯起來。

例子

執行一次dispatch

store.dispatch({type:"ADD_AGE"})

輸出結果以下:
clipboard.png

注意

中間件的串聯並非簡單依次執行,而是從middlewares數組的右邊開始,依次將後一箇中間件輸出當成的函數(一個接收action的函數)做爲前一個的next。

總結

最後,通過採用middleware,咱們加入middleware來實現「非純操做」,如請求異步接口,進隊列,出隊列,處理數據等。

加入middleware後的數據流

clipboard.png

參考文獻

https://zhuanlan.zhihu.com/p/...
https://github.com/reactjs/redux

相關文章
相關標籤/搜索