首先分析一下一个todolist具有哪些功能:
- 具有一个输入框,能够添加待办事项(todoItem),每个待办事项应具有独有的id
- 具有一个展示待办事项的列表,能够删除待办事项,或是直接清空列表
- 待办事项应该具有“状态”,能够标注“已完成”或“未完成”,且具有批量实现的功能
- 能够加上标签,用作筛选
如何根据这些功能抽象出数据结构呢?首先最重要的是待办事项(todoItem)的列表,并附带有是否完成、标签、id等信息,这是核心数据,而另一类数据为筛选数据,提供筛选待办事项的条件,因此state的结构设计如下:
- 待办事项位于列表中,每个选项为一个对象,具有id,completed,color,text等字段
- 筛选条件对象有status和colors两个字段
const initState = {
todos: [
{id: 0, text: 'learn react', completed: true},
{id: 1, text: 'learn vue', completed: false, color: 'green'}
],
filter: {
status: 'Active',
colors: ['red', 'blue']
}
}
现在我们已经有了一个原始数据,如果要声明一个reducer函数,还需要设计action
根据功能,action应包括:添加item,删除item,清空item,状态切换,全部完成,全部未完成,设置标签,删除标签,设置过滤标签选项等,这里不在一一列出了。
action设计完成后,可以创建reducer函数了
const appReducer = (state = initState, action) => {
switch (action.type) {
case 'todos/todoAdded': {
return {
...initState,
todos: [
...state.todos,
{id: nextTodoId(state.todos),
text: action.payload,
completed: false
}
]
}
}
// case: ...
default:
return state
}
}
切记,不可以直接修改state里面的数据,必须通过复制得到一个副本并对副本进行修改来进行(可以使用拓展运算符),因为直接修改可能会导致错误。
因为todolist的功能很复杂,在写reducer函数的时候,为了方便维护,可以根据state的分类进行拆分,分为多个slice,然后通过combineReducers组合起来
import {combineReducers} from 'redux'
const rootReducer = combineReducers({
todos: todosReducer,
filter: filtersReducer
})
创建好根reducer函数后,可以创建store了,将其传入createStore()中生成store
useSelector
接下来是通过react-redux库,结合redux使用react,它提供了一个hook:useSelector ,这个hook接收一个selector函数,这个函数接收store的state作为参数,然后从state中取值并返回
import { useSelector } from 'react-redux'
const selectTodos = state => state.todos
const TodoList = () => {
const todos = useSelector(selectorTodos)
const renderedListItems = todos.map(todo => {
return <TodolistItem key={todo.id} todo={todo}>
})
return <ul className='todo-list'>{renderdListItems}</il>
}
useSelector会自动订阅store,当dispatch 一个 action后,store会更新,如果selector返回的值和上一次相比发生变化,而组件中的数据也会做出相应的更新,并重新渲染视图,但是要注意只要selector返回的结果是新地址引用,组件就会重新渲染,即使数据值没有改变
react组件
useDispatch
上面的操作读取了store中的数据,而下一步就是学习另一个hook,它是用来向store发送action的useDispatch hook,该hook会返回store.dispatch
import { useDispatch } from 'react-redux'
const todoText = () => {
cosnt [text, setText] = useState('')
const dispatch = useDispatch()
const handleChange = e => setText(e.target.value)
const handleKeyDown = e => {
const trimmedText = e.target.value.trim()
if (e.key === 'Enter' && trimmedText ) {
dispatch({type: 'todos/todoAdded', payload: trimmedText})
setText('')
}
}
return ...
}
而除了两个hook,我们还需要在组件外用组件进行包裹,并将store作为prop传递给组件,这样才能在被包裹的组件中访问到store中的数据
使用react-redux主要是有以下关键部分:
- 使用
useSelectorhook 函数来读取 React 组件中的数据 - 使用
useDispatchhook 函数在组件中 dispatch action - 使用
<Provider store={store}>组件包裹<App>组件,这样其他组件都能够和 store 进行交互
通过上述方法,完成了todolist的一个基础设计以及添加待办事项功能的实现,然而一个todolist的功能还是相当复杂的,这里的其他部分不再贴代码,建议移步redux官网学习。可以看到,redux的使用还是相当繁琐的,redux团队也开发了Redux Toolkit,提供了更为简单的方法来编写代码管理状态,后面也可以用新的方法尝试一下