react:實現簡單的redux

簡單的狀態管理器

第一階段

首先我們做一個小功能,點擊按鈕改變數字

/*------count 的發佈訂閱者實踐------*/
let state = {
  count: 1
};
let listeners = [];
/*訂閱*/
function subscribe(listener) {
  listeners.push(listener);
}
function changeCount(count) {
  state.count = count;
  /*當 count 改變的時候,我們要去通知所有的訂閱者*/
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i];
    listener();
  }
}

點擊按鈕觸發:

/*來訂閱一下,當 count 改變的時候,我要實時輸出新的值*/
subscribe(() => {
  console.log(state.count);
});
/*我們來修改下 state,當然我們不能直接去改 state 了,我們要通過 changeCount 來修改*/
changeCount(2);
changeCount(3);
changeCount(4);

這裏所有要用到 state 的函數都是訂閱者,比如上面的 console.log(state.count);,先訂閱完後,再用 changeCount(2); 去改變 state 裏的值
問題:
這個狀態管理器只能管理 count,不通用
公共的代碼要封裝起來

第二階段

封裝公共代碼:

const createStore = function (initState) {
  let state = initState;
  let listeners = [];
  /*訂閱*/
  function subscribe(listener) {
    listeners.push(listener);
  }
  function dispatch(newState) {
    state = newState;
    /*通知*/
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }
  }
  function getState() {
    return state;
  }
  return {
    subscribe,
    dispatch,
    getState
  }
}

使用這個狀態管理器管理多個狀態 counter 和 info 試試

let initState = {
  counter: {
    count: 0
  },
  info: {
    name: '',
    description: ''
  }
}
let store = createStore(initState);
store.subscribe(() => {
  let state = store.getState();
  console.log(`${state.info.name}${state.info.description}`);
});
store.subscribe(() => {
  let state = store.getState();
  console.log(state.counter.count);
});
store.dispatch({
  ...store.getState(),
  info: {
    name: '前端九部',
    description: '我們都是前端愛好者!'
  }
});
store.dispatch({
  ...store.getState(),
  counter: {
    count: 1
  }
});

到這裏我們完成了一個簡單的狀態管理器。
我們的 createStore,提供了 dispatch,getState,subscribe 三個能力。

有計劃的狀態管理器

我們對 count 的修改沒有任何約束,任何地方,任何人都可以修改。
我們需要約束,不允許計劃外的 count 修改

  1. 制定一個 state 修改計劃,告訴 store,我的修改計劃是什麼。
  2. 修改 store.dispatch 方法,告訴它修改 state 的時候,按照我們的計劃修改。

第一階段

我們來設置一個 reducer 函數,接收現在的 state,和一個 action,返回經過改變後的新的 state。

/*注意:action = {type:'',other:''}, action 必須有一個 type 屬性*/
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state;
  }
}

我們把這個計劃告訴 store,store.dispatch 以後改變 state 要按照我的計劃來改。

/*增加一個參數 reducer*/
const createStore = function (reducer, initState) {
  let state = initState;
  let listeners = [];
  function subscribe(listener) {
    listeners.push(listener);
  }
  function dispatch(action) {
    /*請按照我的計劃修改 state*/  
    state = reducer(state, action);
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }
  }
  function getState() {
    return state;
  }
  return {
    subscribe,
    dispatch,
    getState
  }
}

我們來嘗試使用下新的 createStore 來實現自增和自減

let initState = {
  count: 0
}
/*把reducer函數*/
let store = createStore(reducer, initState);
store.subscribe(() => {
  let state = store.getState();
  console.log(state.count);
});
/*自增*/
store.dispatch({
  type: 'INCREMENT'
});
/*自減*/
store.dispatch({
  type: 'DECREMENT'
});
/*我想隨便改 計劃外的修改是無效的!*/
store.dispatch({
  count: 'abc'
});

多文件協作

我們知道 reducer 是一個計劃函數,接收老的 state,按計劃返回新的 state。那我們項目中,有大量的 state,每個 state 都需要計劃函數,如果全部寫在一起會是啥樣子呢?
所有的計劃寫在一個 reducer 函數裏面,會導致 reducer 函數及其龐大複雜。按經驗來說,我們肯定會按組件維度來拆分出很多個 reducer 函數,然後通過一個函數來把他們合併起來。

reducer 的拆分和合並

我們來管理兩個 state,一個 counter,一個 info。

let state = {
  counter: {
    count: 0
  },
  info: {
    name: '前端九部',
    description: '我們都是前端愛好者!'
  }
}

他們各自的 reducer

/*counterReducer, 一個子reducer*/
/*注意:counterReducer 接收的 state 是 state.counter*/
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      }
    default:
      return state;
  }
}
/*InfoReducer,一個子reducer*/
/*注意:countReducer 接收的 state 是 state.info*/
function InfoReducer(state, action) {
  switch (action.type) {
    case 'SET_NAME':
      return {
        ...state,
        name: action.name
      }
    case 'SET_DESCRIPTION':
      return {
        ...state,
        description: action.description
      }
    default:
      return state;
  }
}

我們用 combineReducers 函數來把多個 reducer 函數合併成一個 reducer 函數

const reducer = combineReducers({
    counter: counterReducer,
    info: InfoReducer
});

我們嘗試實現下 combineReducers 函數

function combineReducers(reducers) {
  /* reducerKeys = ['counter', 'info']*/
  const reducerKeys = Object.keys(reducers)
  /*返回合併後的新的reducer函數*/
  return function combination(state = {}, action) {
    /*生成的新的state*/
    const nextState = {}
    /*遍歷執行所有的reducers,整合成爲一個新的state*/
    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i]
      const reducer = reducers[key]
      /*之前的 key 的 state*/
      const previousStateForKey = state[key]
      /*執行 分 reducer,獲得新的state*/
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
    }
    return nextState;
  }
}

完整的調用:

const reducer = combineReducers({
  counter: counterReducer,
  info: InfoReducer
});
let initState = {
  counter: {
    count: 0
  },
  info: {
    name: '前端九部',
    description: '我們都是前端愛好者!'
  }
}
let store = createStore(reducer, initState);
store.subscribe(() => {
  let state = store.getState();
  console.log(state.counter.count, state.info.name, state.info.description);
});
/*自增*/
store.dispatch({
  type: 'INCREMENT'
});
/*修改 name*/
store.dispatch({
  type: 'SET_NAME',
  name: '前端九部2號'
});

state 的拆分和合並

state 我們還是寫在一起的,這樣會造成 state 樹很龐大,不直觀,很難維護。我們需要拆分,一個 state,一個 reducer 寫一塊。

/* counter 自己的 state 和 reducer 寫在一起*/
let initState = {
  count: 0
}
function counterReducer(state, action) {
  /*注意:如果 state 沒有初始值,那就給他初始值!!*/  
  if (!state) {
      state = initState;
  }
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1
      }
    default:
      return state;
  }
}

我們修改下 createStore 函數,增加一行 dispatch({ type: Symbol() })

const createStore = function (reducer, initState) {
  let state = initState;
  let listeners = [];
  function subscribe(listener) {
    listeners.push(listener);
  }
  function dispatch(action) {
    state = reducer(state, action);
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }
  }
  function getState() {
    return state;
  }
  /* 注意!!!只修改了這裏,用一個不匹配任何計劃的 type,來獲取初始值 */
  dispatch({ type: Symbol() })
  return {
    subscribe,
    dispatch,
    getState
  }
}

這一行的效果:

  1. createStore 的時候,用一個不匹配任何 type 的 action,來觸發 state = reducer(state, action)
  2. 因爲 action.type 不匹配,每個子 reducer 都會進到 default 項,返回自己初始化的 state,這樣就獲得了初始化的 state 樹了。

我們來試試

/*這裏沒有傳 initState 哦 */
const store = createStore(reducer);
/*這裏看看初始化的 state 是什麼*/
console.dir(store.getState());

中間件 middleware

中間件是對 dispatch 的擴展,或者說重寫,增強 dispatch 的功能!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章