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:戳我查看原文

相关文章
相关标签/搜索