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會向外拋出getState
、dispatch
、subscribe
這三個接口,方便用戶調用:函數
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
的思想在不少場景中均可以使用到,不過我以爲關鍵還在於理解何時須要使用它,適用的場景中使用能夠優化代碼結構,提升可讀性,不適用的場景反而會讓應用變得複雜,不易理解。