前端数据管理

介绍当前比较流行的几种前端数据管理的解决方案

flux

facebook推出的构建用户界面的应用架构
flux结构

  • 单向数据流,在view层不能直接修改数据,要修改数据只能通过提交action,然后dispatcher到stroe,使得整体数据的变化是可控的。
  • dispatcher作为连接action和store的通道,一个dispatcher必须注册各个action type的回调函数,从而实现,在回调函数中调用stroe的各种函数,让store实现变化。
  • 在view层监听store的变化,从而更改view内部的state
  • 官方实现版本

    个人理解

  • dispatcher是否可以去掉,从flux/util提供的reduceStore来看,也是可以丢掉的。
  • 太繁琐了,需要自己在view层监听store的变化,从而去改变state,应该设计一种根store传递到view,提交action时,修改store,从而直接导致view的变化。

flux/utils

  • flux应该也知道其官方实现的flux太繁琐了,所以进化出flux/utils,其中有几个重要的概念

    • container

      • 控制view,收集信息
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        import AppView from '../views/AppView';
        import {Container} from 'flux/utils';
        import TodoActions from '../data/TodoActions';
        import TodoDraftStore from '../data/TodoDraftStore';
        import TodoEditStore from '../data/TodoEditStore';
        import TodoStore from '../data/TodoStore';

        function getStores() {
        return [
        TodoEditStore,
        TodoDraftStore,
        TodoStore,
        ];
        }

        function getState() {
        return {
        draft: TodoDraftStore.getState(),
        editing: TodoEditStore.getState(),
        todos: TodoStore.getState(),

        onAdd: TodoActions.addTodo,
        onDeleteCompletedTodos: TodoActions.deleteCompletedTodos,
        onDeleteTodo: TodoActions.deleteTodo,
        onEditTodo: TodoActions.editTodo,
        onStartEditingTodo: TodoActions.startEditingTodo,
        onStopEditingTodo: TodoActions.stopEditingTodo,
        onToggleAllTodos: TodoActions.toggleAllTodos,
        onToggleTodo: TodoActions.toggleTodo,
        onUpdateDraft: TodoActions.updateDraft,
        };
        }

        export default Container.createFunctional(AppView, getStores, getState);
    • reduceStore

      • 为了解决之前的store太繁琐。在container执行的时候,会传入action的所有handler,view层手动执行handler,从而触发dispatch。在store的reduce方法中,会针对各种action type返回全新的store
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        //view
        <input
        checked={areAllComplete ? 'checked' : ''}
        id="toggle-all"
        type="checkbox"
        onChange={props.onToggleAllTodos}
        />
        // action
        toggleAllTodos() {
        TodoDispatcher.dispatch({
        type: TodoActionTypes.TOGGLE_ALL_TODOS,
        });
        }
        // store
        case TodoActionTypes.TOGGLE_ALL_TODOS:
        const areAllComplete = state.every(todo => todo.complete);
        return state.map(todo => todo.set('complete', !areAllComplete));
    • 解决了最初版本中,需要在view层监听数据变化,从而触发view更新。

redux

  • 和flux加上flux/util很相似。就三个概念,action描述数据变化,reducer根据action来产生新的state。store就是联系action和reducer的。他会负责dispatch action。dispatch内部会执行reducer方法,从而更新state
  • store有一个subscribe函数,用来注册数据变化的监听函数,dispatch时,会在执行完reducer后,执行subscribe注册的函数。
  • 总的来讲就是比flux简洁许多,整个结构清晰明了。
  • 使用中间件的方式来处理异步数据流,比较有名的有redux-thunkredux-sagaredux-promise
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const reducer = (state = {count: 0}, action) => {
    switch (action.type){
    case 'INCREASE': return {count: state.count + 1};
    case 'DECREASE': return {count: state.count - 1};
    default: return state;
    }
    }

    const actions = {
    increase: () => ({type: 'INCREASE'}),
    decrease: () => ({type: 'DECREASE'})
    }

    const store = createStore(reducer);

    store.subscribe(() =>
    console.log(store.getState())
    );

    store.dispatch(actions.increase()) // {count: 1}
    store.dispatch(actions.increase()) // {count: 2}
    store.dispatch(actions.increase()) // {count: 3}

react-redux

  • 通过provider 在getChildContext 把store挂在context上,子组件就能通过context获取store,解决了用props层层传递的问题
  • 容器组件都是connect过的,connect通过context获取store,然后再map到纯UI组件。
  • 为了避免每个组件都从context上获取数据,又衍生了容器组件和purComponent。并且提供connect方法,把container包装一下,connect去context上获取数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    export default class Provider extends Component {
    getChildContext() {
    return { store: this.store }
    }

    constructor(props, context) {
    super(props, context)
    this.store = props.store
    }

    render() {
    return Children.only(this.props.children)
    }
    }

vuex

  • 使用单一状态树,一个对象就包含了全部的应用层级状态。
  • 几个概念,state(状态)、mutations(同步更改)、actions(异步更改)、getters(store的计算属性)、Module(模块化分割单一状态树)。
  • 为了解决在不同组件中重复声明内部状态和store的联系,增加了mapState、mapMutations、mapGetters、mapActions几个辅助函数。
  • mutation必须是同步的,异步数据用dispatch action来触发异步操作。action中获取要异步数据后使用commit mutation来更新state。利用promise来解决异步数据的管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    actions: {
    actionA ({ commit }) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    commit('someMutation')
    resolve()
    }, 1000)
    })
    }
    }
    store.dispatch('actionA').then(() => {
    // ...
    })
  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    const store = new Vuex.Store({
    state: {
    count: 0
    },
    mutations: {
    increment (state,payload) {
    state.count++
    }
    },
    getters: {
    doneTodos: state => {
    return state.todos.filter(todo => todo.done)
    }
    },
    actions: {
    increment (context) {
    context.commit('increment')
    }
    }
    })

    store.commit('increment', 10)

    const moduleA = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
    }

    const moduleB = {
    state: { ... },
    mutations: { ... },
    actions: { ... }
    }

    const store = new Vuex.Store({
    modules: {
    a: moduleA,
    b: moduleB
    }
    })

Mobx

  • 响应式、依赖收集
  • 和react结合的例子,对视图用@observer装饰器,声明为依赖。数据变化的时候会自动执行数据对应的依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import {observer} from 'mobx-react';

    @observer
    class TimerView extends React.Component {
    render() {
    return (<button onClick={this.onReset.bind(this)}>
    Seconds passed: {this.props.appState.timer}
    </button>);
    }

    onReset () {
    this.props.appState.resetTimer();
    }
    };

    ReactDOM.render(<TimerView appState={appState} />, document.body);

    appState.resetTimer = action(function reset() {
    appState.timer = 0;
    });

    setInterval(action(function tick() {
    appState.timer += 1;
    }), 1000);

rxjs

  • 结合了观察者模式、迭代器模式 和 使用集合的函数式编程,以满足以一种理想方式来管理事件序列所需要的一切。事件驱动、流管理。
  • 结合react的例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import messages from './someObservable';

    class MyComponent extends ObservableComponent {
    constructor(props) {
    super(props);
    this.state = {messages: []};
    }
    componentDidMount() {
    this.messages = messages
    // 在数组中累积我们的消息
    .scan((messages, message) => [message].concat(messages), [])
    // 当得到一条新消息时进行渲染
    .subscribe(messages => this.setState({messages: messages}));
    }
    componentWillUnmount() {
    this.messages.unsubscribe();
    }
    render() {
    return (
    <div>
    <ul>
    {this.state.messages.map(message => <li>{message.text}</li>)}
    </ul>
    </div>
    );
    }
    }

    export default MyComponent;

参考资料