一种简化 Redux 的思路

动机

我们热爱 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)

是不是就简单很多了?只需一个方法,即可定义所有的 actionreducer(以及 异步 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` 上的方法 dispatch 一个同步 action
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