动机
我们热爱 React 和 Redux。但是,Redux 中有太多的样板文件,需要很多的重复劳动,这一点令人沮丧;更别提在实际的 React 应用中,还要集成 react-router
的路由功能了。
一个典型的 React/Redux 应用看起来像下面这样:
actions.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export const ADD_TODO = 'todos/add' export const COMPLETE_TODO = 'todos/complete' export function addTodo(text) { return { type: ADD_TODO, text } } export function completeTodo(id) { return { type: COMPLETE_TODO, id } }
|
reducers.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { ADD_TODO, COMPLETE_TODO } from './actions' let nextId = 0 export default function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [...state, {text: action.text, id: nextId++}] case COMPLETE_TODO: return state.map(todo => { if (todo.id === action.id) todo.completed = true return todo }) default: return state } }
|
Todos.js
1 2 3 4 5 6 7 8 9
| import { addTodo, completeTodo } from './actions' dispatch(addTodo('a new todo')) dispatch(completeTodo(42))
|
看起来是不是有点繁冗?这还是没考虑 异步 action
的情况呢。如果要处理异步 action
,还需要引入 middleware(比如 redux-thunk
或者 redux-saga
),那么代码就更繁琐了。
在一个接口中定义 action/reducer?
Todos.js
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
| import mirror, { actions } from 'mirrorx' let nextId = 0 mirror.model({ name: 'todos', initialState: [], reducers: { add(state, text) { return [...state, {text, id: nextId++}] }, complete(state, id) { return state.map(todo => { if (todo.id === id) todo.completed = true return todo }) } } }) actions.todos.add('a new todo') actions.todos.complete(42)
|
是不是就简单很多了?只需一个方法,即可定义所有的 action
和 reducer
(以及 异步 action
)。
而且,这行代码:
1
| actions.todos.add('a new todo')
|
完全等同于这行代码:
1 2 3 4
| dispatch({ type: 'todos/add', text: 'a new todo' })
|
完全不用关心具体的 action type
,不用写大量的重复代码。简洁,高效。
异步 action
上述代码示例仅仅针对同步 action
。那 异步 action
怎么处理呢?
1 2 3 4 5 6 7 8 9 10
| mirror.model({ effects: { async addAsync(data, getState) { const res = await Promise.resolve(data) actions.todos.add(res) } } })
|
没错,这样就定义了一个异步 action
。上述代码的效果等同于如下代码:
1 2 3 4 5 6
| actions.todos.addSync = (data, getState) => { return dispatch({ type: 'todos/addAsync', data }) }
|
调用 actions.todos.addSync
方法,则会 dispatch 一个 type 为 todos/addAsync
的 action。
当然,处理这样的 action,必须要借助于 middleware。不过实现这样一个 middleware 也非常简单,开发者只管定义 action/reducer,然后简单地调用一个函数就行了。
总结
既然是对现有开发模式做封装和简化,那么要秉承的一个原则应该是,在尽可能地避免发明新的概念,并保持现有开发模式的前提下,减少重复劳动,提高开发效率。
只提供极少数的新 API,其余的都借用 React/Redux/react-router 已有的接口,针对其做封装和强化。
也就是说,不去“颠覆” React/Redux 开发流,只是简化了接口调用,省去样板代码:
针对上面描述的思路,初步完成了一个“框架”,Mirror。