redux源碼學習筆記 - createStore

本篇是學習redux源碼的一些記錄,學習的redux版本是^4.0.1redux

在頁面開發時,須要管理不少狀態(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);
  • reducer是更新store的函數,必傳參數,function類型
  • preloadedState爲初始狀態,爲可選參數。若是reducer是使用 combineReducers 合併多個函數而成的,要注意preloadedState的數據格式要和 combineReducers 中的keys一致。
  • enhancer在學習applyMiddleware時一塊兒說明。

看一下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中的方法能夠訪問到。學習

getState()

只有store.getState()能獲取到倉庫的state --> currentState變量測試

function getState() {
    if (isDispatching) { //··· }
    return currentState
  }

dispatch()

只有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函數的參數狀況:

  • 當前狀態
  • action對象,且有type字段

若是想在state變化時作點什麼,就須要用到subscribe方法添加監聽函數

subscribe(listener)

只看關鍵代碼,其實就是維護了一個保存監聽函數的數組。從上面dispatch的代碼listener()能夠看出,這些函數會在dispatch(action)的時候觸發。

並且每次新增listener的時候都會返回一個取消監聽的方法unsubscribe,能夠在適當的時候取消監聽。

function subscribe(listener) {
    /*-- 增長監聽 --*/
    nextListeners.push(listener)

    /*-- 返回一個取消監聽的函數 --*/
    return function unsubscribe() {

      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

replaceReducer(nextReducer)

能夠更改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()
相關文章
相關標籤/搜索