本篇是學習redux
源碼的一些記錄,學習的redux
版本是^4.0.1
。redux
在頁面開發時,須要管理不少狀態(state),好比服務器響應,緩存數據,UI狀態等等···當頁面的龐大時,狀態就會變的混亂。redux就派上用場了,它最大的特色就是使狀態變化變的可預測。數組
redux提供一個管理state的倉庫(store
),而且規定了store
只能經過reducer
(函數)來更新,而reducer
必須經過dispatch(action)
來觸發,action
就是普通的JavaScript對象,它約定了執行的類型而且傳遞數據。使得state的變化是能夠預測的,一樣的步驟會獲得一樣的state。緩存
從第一步建立倉庫開始看起 createStore(reducer, preloadedState, enhancer)
。服務器
開始已經提到redux是管理一個store,那麼第一步就是建立store,通常最簡單的就是如下形式:閉包
let store = createStore(reducer,preloadedState,enhancer);
看一下createStore 源碼中的關鍵部分:app
// 記錄reducer函數、初始狀態、監聽函數 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] // 建立store時,觸發一個空的action,這樣若是沒有初始狀態,就會返回reducer中的默認狀態 dispatch({ type: ActionTypes.INIT }) // 返回一個提供了多種方法的對象 return { dispatch, //觸發action的方法 subscribe, //增長監聽方法 getState, //獲取當前狀態的方法 replaceReducer, //更換reducer方法 [$$observable]: observable }
這裏是用了閉包,在createStore的做用域中建立了currentState
變量來記錄狀態,currentReducer
來記錄reducer函數,currentListeners
來記錄全部的監聽函數。而後返回一個對象,對象中的方法能夠獲取currentState、觸發reducer來更新currentState,添加監聽函數,替換reducer等。函數
這個對象就是 store , 而state,reducer,listeners保存在createStore的做用域中,只有經過store中的方法能夠訪問到。學習
只有store.getState()
能獲取到倉庫的state --> currentState變量測試
function getState() { if (isDispatching) { //··· } return currentState }
只有store.dispatch(action)
能夠觸發更新state。注意在redux中action必須是一個純對象,並且必須有type字段指定動做類型,dispatch中有對與這些的校驗。spa
function dispatch(action) { /*-- action必須是對象 --*/ if (!isPlainObject(action)) { //··· } /*-- action必須有type字段--*/ if (typeof action.type === 'undefined') { //··· } if (isDispatching) { //··· } /*-- 觸發reducer,更新state --*/ try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } /*-- 執行監聽函數 --*/ const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
從這一段currentState = currentReducer(currentState, action)
看出currentState是經過執行reducer函數更新的。
並且也知道了reducer函數的參數狀況:
若是想在state變化時作點什麼,就須要用到subscribe
方法添加監聽函數
只看關鍵代碼,其實就是維護了一個保存監聽函數的數組。從上面dispatch的代碼listener()
能夠看出,這些函數會在dispatch(action)
的時候觸發。
並且每次新增listener的時候都會返回一個取消監聽的方法unsubscribe
,能夠在適當的時候取消監聽。
function subscribe(listener) { /*-- 增長監聽 --*/ nextListeners.push(listener) /*-- 返回一個取消監聽的函數 --*/ return function unsubscribe() { const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
能夠更改reducer函數,很簡單,從新賦值。
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { //··· } // 更新reducer函數 currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
上面對createStore的實現有了大體的瞭解,再看看栗子
action中須要type
字段來標記要執行的字段,會定義爲字符串常量;通常會使用單獨的模塊來管理。
/*-- 動做類型 actionTypes.js --*/ export const ADD_TODO = 'ADD_LIST'; export const COMPLETE_TODO = 'COMPLETE_TODO';
action 必須是一個純JavaScript對象。能夠經過建立action的函數返回,這樣就方便傳遞數據。
/*-- action對象 action.js --*/ import { ADD_TODO, COMPLETE_TODO} from "./actionTypes"; export function addList (text) { // 返回action對象 return { type: ADD_TODO, text } } export function completeList (id,bl = true) { return { type: COMPLETE_TODO, id, bl } }
reducer 是一個純函數,接受舊的state和action,返回新的state。只要傳入參數相同,返回計算獲得的下一個 state 就必定相同。沒有特殊狀況、沒有反作用,沒有 API 請求、沒有變量修改,單純執行計算。
/*-- reducer函數 todolist.js --*/ import {ADD_TODO, COMPLETE_TODO} from '../actionTypes'; let id = 0; export default function todoList(state = [],action) { // 根據type區分如何更新state switch (action.type) { case ADD_TODO : // 不要直接修改state,要返回一個新的state return [...state,{ id: id++, text: action.text, complete:false }] case COMPLETE_TODO : return state.map( item => { if (item.id === action.id){ return Object.assign({},item,{complete:action.bl}) } return item; }) default: return state; } }
使用 createStore 建立 store
/*-- 建立store --*/ import {createStore} from 'redux'; import reducer from './reducer/todolist'; let store = createStore(reducer);
更新 state
import {addList,completeList} from './store/actions'; store.dispatch(addList('測試數據'));
addList('測試數據')
返回的是 action {type: ADD_TODO,text:'測試數據'}
。 在todoList函數中,執行完後返回新的state [{id:0,text:'測試數據',complete:false}]
。
獲取 state
console.log(store.getState()); // [{id:0,text:'測試數據',complete:false}]
添加/刪除 監聽函數
// 添加 unsubscribe = store.subscribe(function(){ // 每次更新state都會執行此函數 console.log(store.getState()); }); // 取消 unsubscribe()