前言
最近又认真重读了一遍 redux 的文档,不出意料,还是一样的晦涩难懂。
虽然文档写得不怎么样,但是里面确实给了很多比较好的代码组织方式,推荐了很多很有用的工具和插件,也慢慢地理解为什么这么简单的一个状态中心可以搞出这么多概念和库。
redux 文档除了一些概念的介绍,主要包含了
- 怎么只用
redux
这个库来组织 redux 代码 - 怎么用
redux-toolkit
的 API 更智能地组织 redux 代码
redux 文档之所以难以看懂是因为它不按线性的思维来写,很多时候突然就冒出一个概念或者方法,而且总是将 redux
,react-redux
和 redux-toolkit
这三个玩意混在一起讲,搞得看的人是一脸蒙逼。
而这篇文章通过一步步的代码优化来呈现 redux 的最佳写法。(注:这里的最佳写法的范围仅限于 redux 文档,当然还有很多更好的写法这里不讨论)。
这里给出文章的最终代码 https://github.com/learn-redux/learn-redux/tree/master/src/apps/ReactReduxTodo
好了,现在开始我们的探索 redux 之旅吧~
需求 - todo app
我们就以做一个 todo list 来作为我们的需求吧,主要涉及到 todo 的增,删,改,查的操作。对于复杂的页面也只是多个资源的增,删,改,查,所以 todo app 是一个非常好的样例。
app 参照如下
第一版 - 乞丐版的 todo app
乞丐版的意思是,我们只使用 redux 去本地测试里跑 todo app。先搞 reducer.ts
和 store.ts
。
// reducer.ts
const initTodos: TTodo[] = [
{
id: '1',
text: '抽烟',
state: 'done'
},
{
id: '2',
text: '喝酒',
state: 'todo'
},
{
id: '3',
text: '烫头',
state: 'todo'
}
]
const initFilter: TFilter = 'all'
const initState = {
todos: initTodos,
filter: initFilter
}
const reducer = (state = initState , action: any) => {
switch (action.type) {
case 'addTodo':
const newTodos = [...state.todos, action.payload]
return {
...state, todos: newTodos}
case 'removeTodo':
const newTodos = state.todos.filter(todo => todo.id !== action.payload)
return {
...state, todos : newTodos }
case 'toggleTodo':
const newTodos = state.todos.map(todo =>
todo.id === action.payload
? {
...todo, state: todo.state === 'todo' ? 'done' : 'todo'}
: todo
)
return {
...state, todos: newTodos }
case 'setFilter':
return {
...state filter: action.payload }
case 'reset':
return initState
default:
return state
}
}
export default reducer
// store.ts
import {
createStore} from "redux"
import reducer from "./reducer"
const store = createStore(reducer)
store.subscribe(() => console.log('update component'))
export default store
测试代码,因为篇幅问题,这里只展示一个用例。
// app.test.ts
it('可以添加一条 Todo', () => {
const newTodo: TTodo = {
id: '99',
text: '吃好吃的',
state: 'todo',
}
store.dispatch({
type: 'addTodo', payload: newTodo})
const todos = store.getState().todos
expect(todos[todos.length - 1]).toEqual(newTodo)
})
这里测试会正常显示最后一个 todo 就是“吃好吃的”。
这里的 store 主要是 todo 列表和过滤器 filter,代码也很简单,无非就是添加 todo、删除 todo、toggle todo,reset 一些基本操作。
第二版:用 combineReducers 来做 slice
这里注意到在这个 redcuer 里其实包含了对 todos 和 filter 的操作,整个 reducer 看起来很冗长,因此我们会想将 todos 就搞 todosReducer 来管, filter 就用 filterReducer 来管,这种分开管理的子 store 被称为 “slice”。
上面的 reducer 代码可以改写成:
const todosReducer = (todos: TTodo[] = initTodos, action: any) => {
switch (action.type) {
case 'addTodo':
return [...todos, action.payload]
case 'removeTodo':
return todos.