【React系列】從零開始手寫redux

看完這篇,你也能夠實現一個 redux.javascript

clipboard.png

本篇文章對應的代碼在: https://github.com/YvetteLau/... 建議先 clone 代碼,而後對照代碼閱讀本文。html

1. Redux 是什麼?

ReduxJavaScript 狀態容器,提供可預測化的狀態管理。Redux 除了和 React 一塊兒用外,還支持其它界面庫。Redux 體小精悍,僅有 2KB。這裏咱們須要明確一點:ReduxReact 之間,沒有強綁定的關係。本文旨在理解和實現一個 Redux,可是不會涉及 react-redux(一次深刻理解一個知識點便可,react-redux 將出如今下一篇文章中)。前端

2. 從零開始實現一個 Redux

咱們先忘記 Redux 的概念,從一個例子入手,使用 create-react-app 建立一個項目: toreduxjava

代碼目錄: myredux/to-redux 中。react

public/index.htmlbody 修改成以下:git

<div id="app">
    <div id="header">
        前端宇宙
    </div>
    <div id="main">
        <div id="content">你們好,我是前端宇宙做者劉小夕</div>
        <button class="change-theme" id="to-blue">Blue</button>
        <button class="change-theme" id="to-pink">Pink</button>
    </div>
</div>

clipboard.png

咱們要實現的功能如上圖所示,在點擊按鈕時,可以修改整個應用的字體的顏色。github

修改 src/index.js 以下(代碼: to-redux/src/index1.js):redux

let state = {
    color: 'blue'
}
//渲染應用
function renderApp() {
    renderHeader();
    renderContent();
}
//渲染 title 部分
function renderHeader() {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
//渲染內容部分
function renderContent() {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

renderApp();

//點擊按鈕,更改字體顏色
document.getElementById('to-blue').onclick = function () {
    state.color = 'rgb(0, 51, 254)';
    renderApp();
}
document.getElementById('to-pink').onclick = function () {
    state.color = 'rgb(247, 109, 132)'; 
    renderApp();
}

這個應用很是簡單,可是它有一個問題:state 是共享狀態,可是任何人均可以修改它,一旦咱們隨意修改了這個狀態,就能夠致使出錯,例如,在 renderHeader 裏面,設置 state = {}, 容易形成難以預料的錯誤。數組

不過不少時候,咱們又的確須要共享狀態,所以咱們能夠考慮設置一些門檻,好比,咱們約定,不能直接修改全局狀態,必需要經過某個途經才能修改。爲此咱們定義一個 changeState 函數,全局狀態的修改均由它負責。app

//在 index.js 中繼續追加代碼
function changeState(action) {
    switch(action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

咱們約定只能經過 changeState 去修改狀態,它接受一個參數 action,包含 type 字段的普通對象,type 字段用於識別你的操做類型(即如何修改狀態)。

咱們但願點擊按鈕,能夠修改整個應用的字體顏色。

//在 index.js 中繼續追加代碼
document.getElementById('to-blue').onclick = function() {
    let state = changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
    //狀態修改完以後,須要從新渲染頁面
    renderApp(state);
}

document.getElementById('to-pink').onclick = function() {
    let state = changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
    renderApp(state);
}

抽離 store

儘管如今咱們約定了如何修改狀態,可是 state 是一個全局變量,咱們很容易就能夠修改它,所以咱們能夠考慮將其變成局部變量,將其定義在一個函數內部(createStore),可是在外部還須要使用 state,所以咱們須要提供一個方法 getState(),以便咱們在 createStore 獲取到 state

function createStore (state) {
    const getState = () => state;
    return {
        getState
    }
}

如今,咱們能夠經過 store.getState() 方法去獲取狀態(這裏須要說明的是,state 一般是一個對象,所以這個對象在外部實際上是能夠被直接修改的,可是若是深拷貝 state 返回,那麼在外部就必定修改不了,鑑於 redux 源碼中就是直接返回了 state,此處咱們也不進行深拷貝,畢竟耗費性能)。

僅僅獲取狀態是遠遠不夠的,咱們還須要有修改狀態的方法,如今狀態是私有變量,咱們必需要將修改狀態的方法也放到 createStore 中,並將其暴露給外部使用。

function createStore (state) {
    const getState = () => state;
    const changeState = () => {
        //...changeState 中的 code
    }
    return {
        getState,
        changeState
    }
}

如今,index.js 中代碼變成下面這樣(to-redux/src/index2.js):

function createStore() {
    let state = {
        color: 'blue'
    }
    const getState = () => state;
    function changeState(action) {
        switch (action.type) {
            case 'CHANGE_COLOR':
                state = {
                    ...state,
                    color: action.color
                }
                return state;
            default:
                return state;
        }
    }
    return {
        getState,
        changeState
    }
}

function renderApp(state) {
    renderHeader(state);
    renderContent(state);
}
function renderHeader(state) {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
function renderContent(state) {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

document.getElementById('to-blue').onclick = function () {
    store.changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
    renderApp(store.getState());
}
document.getElementById('to-pink').onclick = function () {
    store.changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
    renderApp(store.getState());
}
const store = createStore();
renderApp(store.getState());

儘管,咱們如今抽離了 createStore 方法,可是顯然這個方法一點都不通用,statechangeState 方法都定義在了 createStore 中。這種狀況下,其它應用沒法複用此模式。

changeState 的邏輯理應在外部定義,由於每一個應用修改狀態的邏輯定然是不一樣的。咱們將這部分邏輯剝離到外部,並將其重命名爲 reducer (憋問爲何叫 reducer,問就是爲了和 redux 保持一致)。reducer 是幹嗎的呢,說白了就是根據 action 的類型,計算出新狀態。由於它不是在 createStore 內部定義的,沒法直接訪問 state,所以咱們須要將當前狀態做爲參數傳遞給它。以下:

function reducer(state, action) {
    switch(action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

createStore 進化版

function createStore(reducer) {
    let state = {
        color: 'blue'
    }
    const getState = () => state;
    //將此處的 changeState 改名爲 `dispatch`
    const dispatch = (action) => {
        //reducer 接收老狀態和action,返回一個新狀態
        state = reducer(state, action);
    }
    return {
        getState,
        dispatch
    }
}

不一樣應用的 state 定然是不一樣的,咱們將 state 的值定義在 createStore 內部必然是不合理的。

function createStore(reducer) {
    let state;
    const getState = () => state;
    const dispatch = (action) => {
        //reducer(state, action) 返回一個新狀態
        state = reducer(state, action);
    }
    return {
        getState,
        dispatch
    }
}

你們注意 reducer 的定義,在碰到不能識別的動做時,是直接返回舊狀態的,如今,咱們利用這一點來返回初始狀態。

要想 state 有初始狀態,其實很簡單,我們將初始的 state 的初始化值做爲 reducer 的參數的默認值,而後在 createStore 中派發一個 reducer 看不懂的動做就能夠了。這樣 getState 首次調用時,能夠獲取到狀態的默認值。

createStore 進化版2.0

function createStore(reducer) {
    let state;
    const getState = () => state;
    //每當 `dispatch` 一個動做的時候,咱們須要調用 `reducer` 以返回一個新狀態
    const dispatch = (action) => {
        //reducer(state, action) 返回一個新狀態
        state = reducer(state, action);
    }
    //你要是有個 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是個狠人
    dispatch({ type: `@@redux/__INIT__${Math.random()}` });

    return {
        getState,
        dispatch
    }
}

如今這個 createStore 已經能夠處處使用了, 可是你有沒有以爲每次 dispatch 後,都手動 renderApp() 顯得很蠢,當前應用中是調用兩次,若是須要修改1000次 state 呢,難道手動調用 1000次 renderApp()

能不能簡化一下呢?每次數據變化的時候,自動調用 renderApp()。固然咱們不可能將 renderApp() 寫在 createStore()dispatch 中,由於其它的應用中,函數名未必叫 renderApp(),並且有可能不止要觸發 renderApp()。這裏能夠引入 發佈訂閱模式,當狀態變化時,通知全部的訂閱者。

createStore 進化版3.0

function createStore(reducer) {
    let state;
    let listeners = [];
    const getState = () => state;
    //subscribe 每次調用,都會返回一個取消訂閱的方法
    const subscribe = (ln) => { 
        listeners.push(ln);
        //訂閱以後,也要容許取消訂閱。
        //難道我訂了某本雜誌以後,就不容許我退訂嗎?可怕~
        const unsubscribe = () => {
            listeners = listeners.filter(listener => ln !== listener);
        }
        return unsubscribe;
    };
    const dispatch = (action) => {
        //reducer(state, action) 返回一個新狀態
        state = reducer(state, action);
        listeners.forEach(ln => ln());
        
    }
    //你要是有個 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是個狠人
    dispatch({ type: `@@redux/__INIT__${Math.random()}` });

    return {
        getState,
        dispatch,
        subscribe
    }
}

至此,一個最爲簡單的 redux 已經建立好了,createStoreredux 的核心。咱們來使用這個精簡版的 redux 重寫咱們的代碼,index.js 文件內容更新以下(to-redux/src/index.js):

function createStore() {
    //code(自行將上面createStore的代碼拷貝至此處)
}
const initialState = {
    color: 'blue'
}

function reducer(state = initialState, action) {
    switch (action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}
const store = createStore(reducer);

function renderApp(state) {
    renderHeader(state);
    renderContent(state);
}
function renderHeader(state) {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
function renderContent(state) {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

document.getElementById('to-blue').onclick = function () {
    store.dispatch({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
}
document.getElementById('to-pink').onclick = function () {
    store.dispatch({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
}

renderApp(store.getState());
//每次state發生改變時,都從新渲染
store.subscribe(() => renderApp(store.getState()));

若是如今咱們如今但願在點擊完 Pink 以後,字體色不容許修改,那麼咱們還能夠取消訂閱:

const unsub = store.subscribe(() => renderApp(store.getState()));
document.getElementById('to-pink').onclick = function () {
    //code...
    unsub(); //取消訂閱
}

順便說一句: reducer 是一個純函數(純函數的概念若是不瞭解的話,自行查閱資料),它接收先前的 stateaction,並返回新的 state。不要問爲何 action 中必定要有 type 字段,這僅僅是一個約定而已(redux 就是這麼設計的)

遺留問題:爲何 reducer 必定要返回一個新的 state,而不是直接修改 state 呢。歡迎在評論區留下你的答案。

前面咱們一步一步推演了 redux 的核心代碼,如今咱們來回顧一下 redux 的設計思想:

Redux 設計思想

  • Redux 將整個應用狀態(state)存儲到一個地方(一般咱們稱其爲 store)
  • 當咱們須要修改狀態時,必須派發(dispatch)一個 action( action 是一個帶有 type 字段的對象)
  • 專門的狀態處理函數 reducer 接收舊的 stateaction ,並會返回一個新的 state
  • 經過 subscribe 設置訂閱,每次派發動做時,通知全部的訂閱者。

我們如今已經有一個基礎版本的 redux 了,可是它還不能知足咱們的需求。咱們平時的業務開發不會像上面所寫的示例那樣簡單,那麼就會有一個問題: reducer 函數可能會很是長,由於 action 的類型會很是多。這樣確定是不利於代碼的編寫和閱讀的。

試想一下,你的業務中有一百種 action 須要處理,把這一百種狀況編寫在一個 reducer 中,不只寫得人噁心,後期維護代碼的同事更是想殺人。

所以,咱們最好單獨編寫 reducer,而後對 reducer 進行合併。有請咱們的 combineReducers(和 redux 庫的命名保持一致) 閃亮登場~

combineReducers

首先咱們須要明確一點:combineReducers 只是一個工具函數,正如咱們前面所說,它將多個 reducer 合併爲一個 reducercombineReducers 返回的是 reducer,也就是說它是一個高階函數。

咱們仍是以一個示例來講明,儘管 redux 不是非得和 react 配合,不過鑑於其與 react 配合最爲適合,此處,以 react 代碼爲例:

這一次除了上面的展現之外,咱們新增了一個計數器功能( 使用 React 重構 ===> to-redux2):

//如今咱們的 state 結構以下:
let state = {
    theme: {
        color: 'blue'
    },
    counter: {
        number: 0
    }
}

顯然,修改主題和計數器是能夠分割開得,由不一樣的 reducer 去處理是一個更好的選擇。

store/reducers/counter.js

負責處理計數器的state。

import { INCRENENT, DECREMENT } from '../action-types';

export default counter(state = {number: 0}, action) {
    switch (action.type) {
        case INCRENENT:
            return {
                ...state,
                number: state.number + action.number
            }
        case DECREMENT:
            return {
                ...state,
                number: state.number - action.number
            }
        default:
            return state;
    }
}
store/reducers/theme.js

負責處理修改主題色的state。

import { CHANGE_COLOR } from '../action-types';

export default function theme(state = {color: 'blue'}, action) {
    switch (action.type) {
        case CHANGE_COLOR:
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

每一個 reducer 只負責管理全局 state 中它負責的一部分。每一個 reducerstate 參數都不一樣,分別對應它管理的那部分 state 數據。

import counter from './counter';
import theme from './theme';

export default function appReducer(state={}, action) {
    return {
        theme: theme(state.theme, action),
        counter: counter(state.counter, action)
    }
}

appReducer 便是合併以後的 reducer,可是當 reducer 較多時,這樣寫也顯得繁瑣,所以咱們編寫一個工具函數來生成這樣的 appReducer,咱們把這個工具函數命名爲 combineReducers

咱們來嘗試一下編寫這個工具函數 combineReducers:

思路:
  1. combineReducers 返回 reducer
  2. combineReducers 的入參是多個 reducer 組成的對象
  3. 每一個 reducer 只處理全局 state 中本身負責的部分
//reducers 是一個對象,屬性值是每個拆分的 reducer
export default function combineReducers(reducers) {
    return function combination(state={}, action) {
        //reducer 的返回值是新的 state
        let newState = {};
        for(var key in reducers) {
            newState[key] = reducers[key](state[key], action);
        }
        return newState;
    }
}

reducer 將負責返回 state 的默認值。好比本例中,createStore 中 dispatch({type:@@redux/__INIT__${Math.random()}}),而傳遞給 createStore 的是 combineReducers(reducers) 返回的函數 combination

根據 state=reducer(state,action)newState.theme=theme(undefined, action), newState.counter=counter(undefined, action)countertheme 兩個子 reducer 分別返回 newState.themenewState.counter 的初始值。

利用此 combineReducers 能夠重寫 store/reducers/index.js

import counter from './counter';
import theme from './theme';
import { combineReducers } from '../redux';
//明顯簡潔了許多~
export default combineReducers({
    counter,
    theme
});

咱們寫的 combineReducers 雖然看起來已經可以知足咱們的需求,可是其有一個缺點,即每次都會返回一個新的 state 對象,這會致使在數據沒有變化時進行無心義的從新渲染。所以咱們能夠對數據進行判斷,在數據沒有變化時,返回本來的 state 便可。

combineReducers 進化版
//代碼中省略了一些判斷,默認傳遞的參數均是符合要求的,有興趣能夠查看源碼中對參數合法性的判斷及處理
export default function combineReducers(reducers) {
    return function combination(state={}, action) {
        let nextState = {};
        let hasChanged = false; //狀態是否改變
        for(let key in reducers) {
            const previousStateForKey = state[key];
            const nextStateForKey = reducers[key](previousStateForKey, action);
            nextState[key] = nextStateForKey;
            //只有全部的 nextStateForKey 均與 previousStateForKey 相等時,hasChanged 的值纔是 false
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        //state 沒有改變時,返回原對象
        return hasChanged ? nextState : state;
    }
}

applyMiddleware

官方文檔中,關於 applyMiddleware 的解釋很清楚,下面的內容也參考了官方文檔的內容:

日誌記錄

考慮一個小小的問題,若是咱們但願每次狀態改變前可以在控制檯中打印出 state,那麼咱們要怎麼作呢?

最簡單的便是:

//...
<button onClick={() => {
    console.log(store.getState());
    store.dispatch(actions.add(2));
}}>+</button>
//...

固然,這種方式確定是不可取的,若是咱們代碼中派發100次,咱們不可能這樣寫一百次。既然是狀態改變時打印 state,也是說是在 dispatch 以前打印 state, 那麼咱們能夠重寫 store.dispatch 方法,在派發前打印 state 便可。

let store = createStore(reducer);
const next = store.dispatch; //next 的命令是爲了和中間件的源碼一致
store.dispatch = action => {
    console.log(store.getState());
    next(action);
}

崩潰信息

假設咱們不只僅須要打印 state,還須要在派發異常出錯時,打印出錯誤信息。

const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致
store.dispatch = action => {
    try{
        console.log(store.getState());
        next(action);
    } catct(err) {
        console.error(err);
    }
}

而若是咱們還有其餘的需求,那麼就須要不停的修改 store.dispatch 方法,最後致使這個這部分代碼難以維護。

所以咱們須要分離 loggerMiddlewareexceptionMiddleware.

let store = createStore(reducer);
const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致
const loggerMiddleware = action => {
    console.log(store.getState());
    next(action);
}
const exceptionMiddleware = action => {
    try{
        loggerMiddleware(action);
    }catch(err) {
        console.error(err);
    }
}
store.dispatch = exceptionMiddleware;

咱們知道,不少 middleware 都是第三方提供的,那麼 store 確定是須要做爲參數傳遞給 middleware ,進一步改寫:

const loggerMiddleware = store => action => {
    const next = store.dispatch;
    console.log(store.getState());
    next(action);
}
const exceptionMiddleware = store => action => {
    try{
        loggerMiddleware(store)(action);
    }catch(err) {
        console.error(err);
    }
}

//使用
store.dispatch = exceptionMiddleware(store)(action);

如今還有一個小小的問題,exceptionMiddleware 中的 loggerMiddleware 是寫死的,這確定是不合理的,咱們但願這是一個參數,這樣使用起來才靈活,沒道理只有 exceptionMiddleware 須要靈活,而無論 loggerMiddleware,進一步改寫以下:

const loggerMiddleware = store => next => action => {
    console.log(store.getState());
    return next(action);
}
const exceptionMiddleware = store => next => action => {
    try{
        return next(action);
    }catch(err) {
        console.error(err);
    }
}
//使用
const next = store.dispatch;
const logger = loggerMiddleware(store);
store.dispatch = exceptionMiddleware(store)(logger(next));

如今,咱們已經有了通用 middleware 的編寫格式了。

middleware 接收了一個 next()dispatch 函數,並返回一個 dispatch 函數,返回的函數會被做爲下一個 middlewarenext()

可是有一個小小的問題,當中間件不少的時候,使用中間件的代碼會變得很繁瑣。爲此,redux 提供了一個 applyMiddleware 的工具函數。

上面咱們可以看出,其實咱們最終要改變的就是 dispatch,所以咱們須要重寫 store,返回修改了 dispatch 方法以後的 store.

因此,咱們能夠明確如下幾點:

  1. applyMiddleware 返回值是 store
  2. applyMiddleware 確定要接受 middleware 做爲參數
  3. applyMiddleware 要接受 {dispatch, getState} 做爲入參,不過 redux 源碼中入參是 createStorecreateStore 的入參,想一想也是,沒有必要在外部建立出 store,畢竟在外部建立出的 store 除了做爲參數傳遞進函數,也沒有其它做用,不如把 createStorecreateStore 須要使用的參數傳遞進來。
//applyMiddleWare 返回 store.
const applyMiddleware = middleware => createStore => (...args) => {
    let store = createStore(...args);
    let middle = loggerMiddleware(store);
    let dispatch = middle(store.dispatch); //新的dispatch方法
    //返回一個新的store---重寫了dispatch方法
    return {
        ...store,
        dispatch
    }
}

以上是一個 middleware 的狀況,可是咱們知道,middleware 多是一個或者是多個,並且咱們主要是要解決多個 middleware 的問題,進一步改寫。

//applyMiddleware 返回 store.
const applyMiddleware = (...middlewares) => createStore => (...args) => {
    let store = createStore(...args);
    let dispatch;
    const middlewareAPI = {
        getState: store.getstate,
        dispatch: (...args) => dispatch(...args)
    }
    //傳遞修改後的 dispatch
    let middles = middlewares.map(middleware => middleware(middlewareAPI));
    //如今咱們有多個 middleware,須要屢次加強 dispatch
    dispatch = middles.reduceRight((prev, current) => current(prev), store.dispatch);
    return {
        ...store,
        dispatch
    }
}

不知道你們是否是理解了上面的 middles.reduceRight,下面爲你們細緻說明一下:

/*三個中間件*/
let logger1 = ({dispatch, getState}) => dispatch => action => {
    console.log('111');
    dispatch(action);
    console.log('444');
}
let logger2 = ({ dispatch, getState }) => dispatch => action => {
    console.log('222');
    dispatch(action);
    console.log('555')
}
let logger3 = ({ dispatch, getState }) => dispatch => action => {
    console.log('333');
    dispatch(action);
    console.log('666');
}
let middle1 = logger1({ dispatch, getState });
let middle2 = logger2({ dispatch, getState });
let middle3 = logger3({ dispatch, getState });

//applyMiddleware(logger1,logger2,logger3)(createStore)(reducer)
//若是直接替換
store.dispatch = middle1(middle2(middle3(store.dispatch)));

觀察上面的 middle1(middle2(middle3(store.dispatch))),若是咱們把 middle1,middle2,middle3 當作是數組的每一項,若是對數組的API比較熟悉的話,能夠想到 reduce,若是你還不熟悉 reduce,能夠查看MDN文檔

//applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)

//reduceRight 從右到左執行
middles.reduceRight((prev, current) => current(prev), store.dispatch);
//第一次 prev: store.dispatch    current: middle3  
//第二次 prev: middle3(store.dispatch) current: middle2
//第三次 prev: middle2(middle3(store.dispatch))  current: middle1
//結果 middle1(middle2(middle3(store.dispatch)))

閱讀過 redux 的源碼的同窗,可能知道源碼中是提供了一個 compose 函數,而 compose 函數中沒有使用 reduceRight,而是使用的 reduce,於是代碼稍微有點不一樣。可是分析過程仍是同樣的。

compose.js

export default function compose(...funcs) {
    //若是沒有中間件
    if (funcs.length === 0) {
        return arg => arg
    }
    //中間件長度爲1
    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
}

關於 reduce 的寫法,建議像上面的 reduceRight 同樣,進行一次分析

使用 compose 工具函數重寫 applyMiddleware

const applyMiddleware = (...middlewares) => createStore => (...args) => {
    let store = createStore(...args);
    let dispatch;
    const middlewareAPI = {
        getState: store.getstate,
        dispatch: (...args) => dispatch(...args)
    }
    let middles = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...middles)(store.dispatch);
    return {
        ...store,
        dispatch
    }
}

bindActionCreators

redux 還爲咱們提供了 bindActionCreators 工具函數,這個工具函數代碼很簡單,咱們不多直接在代碼中使用它,react-redux 中會使用到。此處,簡單說明一下:

//一般咱們會這樣編寫咱們的 actionCreator
import { INCRENENT, DECREMENT } from '../action-types';

const counter = {
    add(number) {
        return {
            type: INCRENENT,
            number
        }
    },
    minus(number) {
        return {
            type: DECREMENT,
            number
        }
    }
}

export default counter;

在派發的時候,咱們須要這樣寫:

import counter from 'xx/xx';
import store from 'xx/xx';

store.dispatch(counter.add());

固然,咱們也能夠像下面這樣編寫咱們的 actionCreator:

function add(number) {
    return {
        type: INCRENENT,
        number
    }
}

派發時,須要這樣編寫:

store.dispatch(add(number));

以上代碼有一個共同點,就是都是 store.dispatch 派發一個動做。所以咱們能夠考慮編寫一個函數,將 store.dispatchactionCreator 綁定起來。

function bindActionCreator(actionCreator, dispatch) {
    return  (...args) => dispatch(actionCreator(...args));
}
function bindActionCreators(actionCreator, dispatch) {
    //actionCreators 能夠是一個普通函數或者是一個對象
    if(typeof actionCreator === 'function') {
        //若是是函數,返回一個函數,調用時,dispatch 這個函數的返回值
        bindActionCreator(actionCreator, dispatch);
    }else if(typeof actionCreator === 'object') {
        //若是是一個對象,那麼對象的每一項都要都要返回 bindActionCreator
        const boundActionCreators = {}
        for(let key in actionCreator) {
            boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
        }
        return boundActionCreators;
    }
}

在使用時:

let counter = bindActionCreators(counter, store.dispatch);
//派發時
counter.add();
counter.minus();

這裏看起來並無精簡太多,後面在分析 react-redux 時,會說明爲何須要這個工具函數。

至此,個人 redux 基本已經編寫完畢。與 redux 的源碼相比,還相差一些內容,例如 createStore 提供的 replaceReducer 方法,以及 createStore 的第二個參數和第三個參數沒有說起,稍微看一下代碼就能懂,此處再也不一一展開。

參考連接

  1. React.js 小書
  2. redux中文文檔
  3. 徹底理解 redux(從零實現一個 redux)

clipboard.png

相關文章
相關標籤/搜索