Redux 基礎與實踐

以前寫過一篇 Regular 組件開發的一些建議 的文章提到了平常開發Regular組件的一些槽點,並提出了在簡單需求,不使用狀態管理框架時的一些替代方案。本文的目的即是填前文的一個坑,即較複雜需求下 Redux 引入方案。
html

關於 Redux

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。前端

看一個簡單的 DEMO

const store = redux.createStore(function(prevState, action) {
    if (!prevState) {
        prevState = {
            count: 0
        };
    }

    switch(action.type) {
        case 'REQUEST':
            return {
                count: prevState.count + 1
            }
    }

    return prevState;
});

store.dispatch({
    type: 'REQUEST'
});

store.subscribe(function () {
    console.log(store.getState());
});
複製代碼

理解他,咱們能夠結合後端 MVC 的 web 模型
web架構
redux架構git

演員表

Store 飾演 應用服務器

const store = redux.createStore(f);
複製代碼

createStore 這個 API 會建立一臺應用服務器,包含數據庫存儲,以及一個 web Servergithub

State 飾演 DataBase

const state = store.getState()
複製代碼

getState 操做會返回當前的服務器數據庫數據,這臺數據庫 bind 了 0.0.0.0(閉包),因此外界沒法操做關於數據庫的信息,只能經過內部的服務對數據庫作修改web

Action 飾演請求

store.dispatch({
    type: 'type',
    payload: {}
})
複製代碼

dispatch(action) 的操做就像是往服務器發送請求。action.type 就像是請求的 uri,action.payload 就像是請求的 body/query。數據庫

Reducer 飾演 Controller + Service + DAO

const reducer = function (prevState, action) {
    if (!prevState) {
        return {}
    }
    switch(action.type) {
        // 分發處理
    }
    return prevState;
}
redux.createStore(reducer);
複製代碼

相應請求會進入對應的控制器(就像 reducer 的 switch 判斷),而控制器內也會對請求攜帶的信息(payload)作分析,來實現對數據庫的增刪改查。redux

設計原則

單一數據源

整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。小程序

通常狀況下 createStore 建立的 store ,將做用於整個應用後端

State 是隻讀的

惟一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。微信小程序

避免人爲的操做 state 變量,形成狀態變化不可預測的狀況,人爲修改 state 的方式被切斷了。

至於如何實現,點我看源碼

createStore 的操做,會建立一個內部變量 state, 因爲 return 出來的 dispatchsubscribe 方法保持了對 state 變量的引用,因此 state 會以閉包的形式存活下來。

使用純函數來執行修改

爲了描述 action 如何改變 state tree ,你須要編寫 reducers。

使用純函數,可測試性和可維護性獲得了保障。

完成與視圖層的綁定

Redux 職責是狀態管理,並不是只限定於前端開發,要使用到 web 開發當中,還缺乏一個部分,完成 MVC 中剩下的一些操做(渲染頁面)。

web架構
v-redux架構

兩個重要的 API

  • store.subscribe(f) - 發佈訂閱模型, 方法 f 會在 dispatch 觸發後執行
  • store.getState() - return 出當前完整的 state 樹

簡單粗暴的方式

const store = redux.createStore(reducer);
const ComponentA = Regular.extend({
    config() {
        const ctx = this;
        store.subscribe(function () {
            const state = store.getState();
            const mapData = {
                clicked: state.clicked,
            }
            Object.assign(ctx.data, mapData);
            ctx.$update();
        });
    }
});
複製代碼

兩個問題

  1. Store 獲取
  2. mapData 與 $update 操做不可複用

更優雅的綁定方式

<StoreProvier store={store}>
    <Component></Component>
</StoreProvier>
複製代碼
const Component = connect({
    mapState(state) {
        return {
            clicked: state.clicked,
        }
    }
})(Regular.extend({
    config() {
    }
}))
複製代碼

1. StoreProvier

Regular.extend({
    name: 'StoreProvider',
    template: '{#include this.$body}',
    config({store} = this.data) {
       if (!store) {
           throw new Error('Provider expected data.store to be store instance created by redux.createStore()')
       }
       
       store.subscribe(() => {
           this.$update();
       });
    }
})
複製代碼

2. connect

統一從最外層的 StoreProvider 組件獲取 store,保證單一數據源

function getStore(ctx) {
    let parent = ctx.$parent;
    while(true) {
        if (!parent) {
            throw new Error('Expected root Component be Provider!')
        }

        if (parent.data.store) {
            return parent.data.store;
        }

        parent = parent.$parent;
    }
}

function connect({
    mapState = () => ({}),
    dispatch
} = {}) {
    return (Component) => Component.implement({
        events: {
            $config(data = this.data) {
                const store = getStore(this);
                const mapStateFn = () => {
                    const state = store.getState();
                    const mappedData = mapState.call(this, state);
                    mappedData && Object.assign(this.data, mappedData);
                }
                mapStateFn();

                const unSubscribe = store.subscribe(mapStateFn);
                
                if (dispatch) {
                    this.$dispatch = store.dispatch;
                }
                
                this.$on('destroy', unSubscribe);
            } 
        }
    });
}
複製代碼

至此回過頭看 regualr-redux 的架構圖,發現正是 StoreProvierconnect 操做幫助 redux 完成了與 MVC 相差的更新視圖操做。

總結

藉助 redux 與特定框架的鏈接器,咱們會發現,對特定 MVVM 框架的要求會變得很低 -- mapState 操做能夠完成 相似 Vue 中 computed/filter 的操做。

因此,現在還在半殘廢中的微信小程序也很適合基於這套思路來結合 redux (逃)。

帶來的好處是,你能夠安心維護你的模型層,不用擔憂 data 過大致使的髒值檢查緩慢,也不須要考慮一些邏輯相關的數據不放入 data 那該放入何處。

相信閱讀此文,你會對 Redux 解決問題的方式有了必定的認識,而繼續深刻的一些方向有:
* middleway 實現原理,redux-logger 和 redux-thunk 等實現方案;
* State 範式化
* Presentational and Container Components
* 單頁的 redux 架構設計

全文完 ;)

by 君羽

PS:戳我查看原文

相關文章
相關標籤/搜索