fengliner / blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Redux架构优化之如何减少样板代码

fengliner opened this issue · comments

“redux很好,但是有太多的样板代码了” 。

这是大多数使用redux的同学的吐槽点。也正因为此,才诞生了那么多基于redux的优化方案。

1. action utilities for redux: redux-actions

redux-actions 为首的 函数工具库

这类方案通常是利用一个生成action creator的函数或者生成reducer creator的函数来减少使用redux中的样板代码。

1.1 定义makeActionCreator函数

可以减少简单的action creator函数,对于复杂的(异步)action creator函数不太好处理

function makeActionCreator(type, ...argNames) {
  return function(...args) {
    let action = { type }
    argNames.forEach((arg, index) => {
      action[argNames[index]] = args[index]
    })
    return action
  }
}

const ADD_TODO = 'ADD_TODO'
const EDIT_TODO = 'EDIT_TODO'
const REMOVE_TODO = 'REMOVE_TODO'

export const addTodo = makeActionCreator(ADD_TODO, 'todo')
export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'todo')
export const removeTodo = makeActionCreator(REMOVE_TODO, 'id')

1.2 定义createReducer函数

把每个switch case封装成函数,本质上还是需要对每个action type进行处理

function createReducer(initialState, handlers) {
    return function reducer(state = initialState, action) {
        if (handlers.hasOwnProperty(action.type)) {
            return handlers[action.type](state, action)
        } else {
            return state
        }
    }
}

const todosreducer = createReducer([], {
    'ADD_TODO': addTodo,
    'TOGGLE_TODO': toggleTodo,
    'EDIT_TODO': editTodo
});

1.3 redux-actions

提供API:createActions handleActionscombineActions,异步action需要结合redux-promise使用

import { createActions, handleActions, combineActions } from 'redux-actions'

const defaultState = { counter: 10 };

const { increment, decrement } = createActions({
  INCREMENT: amount => ({ amount }),
  DECREMENT: amount => ({ amount: -amount })
});

const reducer = handleActions({
  [combineActions(increment, decrement)](state, { payload: { amount } }) {
    return { ...state, counter: state.counter + amount };
  }
}, defaultState);

export default reducer;

2. framework based on redux and redux-saga: dva

基于redux+react-router+redux-saga的封装的一整套解决方案的dva或类dva,比如mirrormickey

核心方法是app.model,用于reducer、initialState、action、saga封装到一起

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: [
    function(dispatch) {
      dispatch({type: 'products/query'});
    },
  ],
  effects: {
    ['products/query']: function*() {
      yield call(delay(800));
      yield put({
        type: 'products/query/success',
        payload: ['ant-tool', 'roof'],
      });
    },
  },
  reducers: {
    ['products/query'](state) {
      return { ...state, loading: true, };
    },
    ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
    },
  },
});

3. Simple, scalable state management: mobx

mobx已经不是redux的优化方案了,可以说是替代方案

  • mobx将state包装成可观察对象(observable)的值,可以在state改变的时候得到更新的值
  • mobx 允许直接修改state,并且是多store,actions也是可选的

在一个地方保存state,通过注解观察state,一旦state修改组件会自动的更新

import {observable, autorun} from 'mobx';

var todoStore = observable({
    /* 一些观察的状态 */
    todos: [],

    /* 推导值 */
    get completedCount() {
        return this.todos.filter(todo => todo.completed).length;
    }
});

/* 观察状态改变的函数 */
autorun(function() {
    console.log("Completed %d of %d items",
        todoStore.completedCount,
        todoStore.todos.length
    );
});

/* ..以及一些改变状态的动作 */
todoStore.todos[0] = {
    title: "Take a walk",
    completed: false
};
// -> 同步打印 'Completed 0 of 1 items'

todoStore.todos[0].completed = true;
// -> 同步打印 'Completed 1 of 1 items'

4. High level abstraction between React and Redux: kea

基于redux、redux-saga、reselect定义logic文件,通过注解的方式引用logic文件里定义的actions、reducers

const logic = kea({
  actions: () => ({
    increment: (amount) => ({ amount }),
    decrement: (amount) => ({ amount })
  }),

  reducers: ({ actions }) => ({
    counter: [0, PropTypes.number, {
      [actions.increment]: (state, payload) => state + payload.amount,
      [actions.decrement]: (state, payload) => state - payload.amount
    }]
  }),

  selectors: ({ selectors }) => ({
    doubleCounter: [
      () => [selectors.counter],
      (counter) => counter * 2,
      PropTypes.number
    ]
  })
})

class Counter extends Component {
  render () {
    const { counter, doubleCounter } = this.props
    const { increment, decrement } = this.actions

    return <div>...</div>
  }
}

export default logic(Counter)

小结

  • redux-actions:从action层面解决redux样板代码的问题,仅仅是一个库(library)
  • dvakea:不同于redux-actions只提供一个简单的库(library),二者都是基于redux及redux的一些异步方案(redux-sagaredux-thunk等)封装的一套框架(framework)。而且二者的设计理念也很相似,都是把action、reducer、saga封装到一个logic或model里,然后把logic或model和组件connect起来进行引用。不同的是kea大量使用了注解(decorator),进一步减少样板代码。
  • mobx:redux的替代方案,不同于redux的纯函数(pure function)的设计,深受面向对象(oo)和命令式编程( imperative)的影响

“redux很好,但是有太多的样板代码了” 。
现在可以不用再吐槽了。