淺析redux

redux適用於不少場景,須要用到全局存儲狀態的應用均可以用到它,無論是換膚的應用仍是購物車的場景,須要將不一樣的組件經過相同的狀態關聯起來,或者相同狀態的變化觸發不一樣視圖的更新,都很適合用到redux。

這篇文章經過實現一個簡單的redux,理解redux是怎樣把狀態和視圖關聯起來的,接下來會實現redux這幾個接口:redux

export { 
  createStore, // 建立store,接受reducer函數和初始化的狀態state
  combineReducer, // 合併多個reducer
  bindActionCreator, // 轉換action對象
}

redux會有一個存放和管理狀態的函數reducer,用戶更改狀態需經過dispatch才能修改裏面的狀態,這樣能避免用戶直接修改state:app

import * as TYPES from '../typings'
function reducer(state,action){
  switch(action.type){
    case TYPES.ADD:
      return {...state,action.payload}
    default:
      return state;
  }
}
這裏能夠看出, action是一個對象,並且必須有 type屬性, reducer經過判斷 type屬性對state進行不一樣的合併操做並返回更新後的 state。另外,這裏還經過 typings文件維護不一樣的 type變量。

接下來是建立createStore函數,store會向外拋出getStatedispatchsubscribe這三個接口,方便用戶調用:函數

export default function createStore(reducer,initState){

  return {
    getState, // 獲取最新state狀態
    dispatch, // 觸發狀態更新,接受參數對象{type}
    subscribe, // 訂閱視圖更新函數
  }
}

針對這個目標,咱們就開始編寫內部函數,首先是建立做用域內的state,將初始值initState賦值給它,接着getState函數固然就是返回它了:優化

let state = initState;
  const getState = ()=> state;

dispatch方法須要接收一個帶有type屬性的對象action,方便reducer函數調用:this

const dispatch = (action)=>{
  
    if(!isPlainObject(action)){
      throw new Error('action 必須是純對象')
    }
    if(typeof action.type == "undefined"){
      throw new Error('必須定義action.type屬性');
    }

    state = reducer(state, action);
    
    return action
  }

這裏看源碼的時候還會判斷action是否爲純對象,這裏附上isPlainObject的實現:spa

function isPlainObject(obj) {
  if(typeof obj !=="object" || obj == null) return false;
  
  let objPro = obj;
  
  // 這裏拿到obj最初始的__proto__
  while (Object.getPrototypeOf(objPro)) {
    objPro = Object.getPrototypeOf(objPro);
  }

  if (objPro === Object.getPrototypeOf(obj)) return true
  
}

接着是實現subscribe函數,方便用戶更新視圖時調用:code

let listeners=[];
 const subscribe = (listener)=>{
      listeners.push(listener);
      let subscribed = true;
      return ()=>{
        if(!subscribed) return;
        let index = listeners.indexOf(listener);
        listeners.splice(index,1);
        subscribed = false;
      }
  }

而後dispatch函數裏面,每次更新完state再執行下listeners裏面存放的訂閱方法:對象

const dispatch = (action)=>{
    ...

    state = reducer(state, action);
    // 更新完state,緊接着觸發訂閱方法
    listeners.forEach(fn=>fn());
    ...
  }

三個函數實現後,基本就完成了,不過咱們還須要在函數內部執行下dispatch方法,初始化用戶傳過來的state值:接口

dispatch({ type: "@@@Redux/INIT"})

ok,這樣這個簡單版的createStore函數就算完成了。作用域

接下來,實現combineReducer函數,這個函數是爲了合併多個reducer時使用的:

function combineReducer(reducers) {
  let reducersKeys = Object.keys(reducers);
  return function(state={},actions){
    let combineState = {};
    for(let i=0;i<reducersKeys.length;i++){
      let key = reducersKeys[i]; // 拿到每一個reducer的key值
      let reducer = reducers[key]; // 拿到每一個reducer方法
      combineState[key] = reducer(state[key], actions);
    }
    return combineState;
  }
}

combineReducer的實現方式有不少,查閱資料的時候也看到有其它的版本,不過咱們只有理解最終返回的值,是一個合併後的大reducer,照着reducer的參數和返回值來寫就好理解了。

還有最後一個bindActionCreator方法,目的是將actions對象轉換,把屬性函數替換成可以執行的dispatch方法:

function bindActionCreator(actions,dispatch) {
  if(typeof actions === "function"){
    return function(){
      dispatch(actions.apply(this,arguments))
    }
  }
  let actionCreator = {};
  for(let name in actions){
    actionCreator[name] = function(){
      dispatch(actions[name].apply(this, arguments))
    }
  }
  return actionCreator;
}

這樣,一個簡單的redux就實現了,redux的思想在不少場景中均可以使用到,不過我以爲關鍵還在於理解何時須要使用它,適用的場景中使用能夠優化代碼結構,提升可讀性,不適用的場景反而會讓應用變得複雜,不易理解。

相關文章
相關標籤/搜索