About
大家好,我是且陶陶,今天跟大家分享一个redux的todoList案例,通过这个案例能够快速掌握redux的基本知识点🌹
❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...❤️...
前情回顾 - 什么是redux 🪷
最流行的状态管理工具之一。(类似于 vue中的vuex)
Redux和React是两个独立的工具/
三个核心概念🌟
- action(动作/行为):【对象格式】描述要做的事(例如:登陆、退出、增删改查等等...)
- reducer(函数):【函数格式 function reducer(state = 0,action){ } 】更新状态
- store(仓库):整合action(动作)和reduce(函数)
store分配要做的事action
给reducer
🍬TodoMVC案例
代码地址🍻:
TodoMvc
欢迎大家批评指正~
功能介绍 🌺
🍦 添加事项
🍦 删除事项
🍦 完成or未完成事项
🍦 全选反选
🍦 清空
🍿 静态结构
🍰 状态管理 - redux
一、创建store📂
-
在
store/reducer/todos.js
中处理行为jsconst initList = [ { id: 1, name: '学习日语,备考N1', isDone: true }, { id: 2, name: '学习英语,备考雅思', isDone: false }, { id: 3, name: '学习GO,找工作', isDone: false }, ] export default function todosReducer(state = initList, sction) { return state }
-
在
store/reducers/index.js
中合并单独的reducer并导出js// 模块合并 并导出 import todos from './todo' import { combineReducers } from 'redux' const rootReducer = combineReducers({ todos }) export default rootReducer
-
在
store/index.js
中挂载 reducer和actionjs// 创建仓库,挂载reducers 并导出 import { createStore } from 'redux' import reducers from './reducers/index' // 创建store const store = createStore(reducers) export default store
二、引入redux🧊
在index.jsx
中,引入redux
和react-redux
用Provider包裹根组件,并提供store值
js
import ReactDOM from 'react-dom/client'
import App from './App'
import store from './store/index'
import { Provider } from 'react-redux'
import './styles/base.css'
import './styles/index.css'
// 渲染UI界面
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(
<Provider store={store}>
<App></App>
</Provider>
)
三、使用仓库状态📉
-
在
components/TodoMain.jsx
【列表内容组件】中,使用 useSelector, useDispatch 这两个hook 操作状态。jsimport React from 'react' import TodoItem from './TodoItem' import { useSelector, useDispatch } from 'react-redux' export default function TodoMain() { // 拿到状态 const todos = useSelector((state) => state.todos) **console.log(todos)** // 修改状态 const dispatch = useDispatch() ... ...
更改状态🍥
步骤
- 界面绑定onChange事件,dispatch触发行为。
- 定义一个action行为,声明actionType
- 根据行为在todosReducer中处理状态
功能实现🍹
界面渲染🕸️
渲染 事项📋
- 在
TodoMain.jsx
中。循环渲染todolist
中的每一项。传递每一项item
js
...
...
return (
<section className="main">
<input id="toggle-all" className="toggle-all" type="checkbox" />
<label htmlFor="toggle-all">Mark all as complete</label>
<ul className="todo-list">
{/* todolist的每一项 */}
**{todos.map((item) => {
return <TodoItem key={item.id} todos={item}></TodoItem>
})}**
</ul>
</section>
)
-
在
TodoItem.jsx
子组件中接收每一项。并渲染- 划线样式类名:completed
- 展示输入框类名:editing
jsexport default function TodoItem(**props**) { const todoitem = props.todos return ( // completed - 划线,已完成事项 // editing - 输入事项 <li className={todoitem.done ? 'completed' : ''}> <div className="view"> {/* 复选框设置选中状态 */} <input className="toggle" type="checkbox" checked={todoitem.isDone} /> <label>{todoitem.name}</label> <button className="destroy"></button> </div> <input className="edit" /> </li> ) }
做到这里,我们会发现控制台报错:
意思是我们这里添加了checked属性,但是需要添加一个change
事件。所以接下来需要添加change
事件。
修改单项🐣
| 💡 选择要修改的项目的复选框,然后改变checked状态。
添加事件🐥
因为当前是受控组件,无法修改。所以需要给他一个onChange
事件
onChange
事件交给store去修改数据。
思路:
- 绑定
onChange
事件,在这个事件中用dispatch
触发action
行为 - 定义一个
action
行为 - 声明
actionTypes
- 根据行为在
todosReducer
里面处理状态
代码:
-
绑定
onChange
事件- 传递id和当前状态
js<input className="toggle" type="checkbox" checked={todoitem.isDone} onChange={() => { dispatch(changeDone(todoitem.id, !todoitem.isDone)) }} />
-
定义action行为
jsimport { CHANGE_STATE } from '../constants/todo' // 修改单个状态的行为 export const changeDone = (id) => { return { type: CHANGE_STATE, id, } }
-
声明
actionType
js// 声明 constantTypes export const CHANGE_STATE = 'todos/changeDone' // 修改单个复选框状态类型
-
todosReducer
里面处理状态jscase CHANGE_STATE: // 注意:状态不可变 return state.map((item) => { if (item.id === action.id) { return { ...item, isDone: action.isDone, } } else { return item } })
-
使用
dispatch
触发action
jsimport React from 'react' import { useDispatch } from 'react-redux' ... export default function TodoItem(props) { ... const dispatch = useDispatch() return ( ... <input className="toggle" type="checkbox" checked={todoitem.isDone} onChange={() => { **dispatch**(changeDone(todoitem.id, !todoitem.isDone)) }} /> ... ) }
删除单项🐤
| 💡 获取要删除的那一项的id,然后过滤掉它。
思路:
- 给X绑定点击事件
onClick
- 定义一个
action
行为 - 声明
actionTypes
- 根据行为在
todosReducer
里面处理状态
代码:
-
给X绑定点击事件
onClick
js<button className="destroy" onClick={() => { dispatch(delTodo(todoitem.id)) }} ></button>
-
定义一个
action
行为js// 删除单个代办项 export const delTodo = (id) => { return { type: DELETE_TODO, id, } }
-
声明
actionTypes
jsexport const DELETE_TODO = 'todos/delTodo' // 删除单个待办
-
根据行为在
todosReducer
里面处理状态jscase DELETE_TODO: return state.filter((item) => { // 过滤掉与选择的这一行相同的id return item.id !== action.id })
添加单项🦜
| 💡 首先对拿到的做非空校验;然后数组添加一项数据。
-
绑定
onChange
事件,得到输入框的输入内容jsimport React, { useState } from 'react' import { useDispatch } from 'react-redux' import { addTodo } from '../store/actions/todo' export default function TodoHeader() { **const [inputValue, setInputValue] = useState('') // 添加单项todo const addValue = (e) => { setInputValue(e.target.value) }** return ( <header className="header"> <h1>todos</h1> <input className="new-todo" placeholder="今天做什么?" value={inputValue} autoFocus **onChange={addValue}** /> </header> ) }
-
绑定
onKeyDown
事件,键盘按下时传递输入项value
js<input className="new-todo" placeholder="今天做什么?" value={inputValue} autoFocus onChange={addValue} onKeyDown={(e) => { if (e.key === 'Enter') { console.log('回车', inputValue) dispatch(addTodo(inputValue)) setInputValue('') // 清空输入框 } }} />
-
定义一个
action
行为js// 添加单个待办项 export const addTodo = (inputValue) => { return { type: ADD_TODO, name: inputValue, } }
-
声明
actionTypes
jsexport const ADD_TODO = 'todos/addTodo' // 添加单个待办项
-
根据行为在
todosReducer
里面处理状态jscase ADD_TODO: if (!action.name.trim()) return // 状态不可变!!! return [ { id: state.length + 1, name: action.name, isDone: false, }, ...state, ]
-
底部筛选🐩
<aside>
💡 要实现底部筛选,可以在footer中使用过滤器进行分发。
</aside>
一、列表项绑定筛选后数据
-
声明
actionTypes
js// 筛选栏标题 export const SHOW_ALL = 'show_all' export const SHOW_COMPLETED = 'show_completed' export const SHOW_ACTIVE = 'show_active' // 筛选行为 export const SET_VISIBILITY_FILTER = 'todos/setVisibilityFilter'
-
定义筛选栏标签的静态数据
jsimport { SHOW_ALL,SHOW_ACTIVE,SHOW_COMPLETED } from "./todo"; export const FILTER_TITLES = { [SHOW_ALL]: 'All', [SHOW_ACTIVE]: 'Active', [SHOW_COMPLETED]: 'Completed' }
-
定义一个
action
行为j// 底部筛选栏 - 用于更新Redux store中的过滤状态 export const setVisibilityFilter = (filter) => ({ type: SET_VISIBILITY_FILTER, filter })
-
根据行为在
todosReducer
里面处理状态- 新建一个
reducer/filter.js
jsimport { SET_VISIBILITY_FILTER } from '../constants/todo' import { SHOW_ALL } from '../constants/todo' // 设置已完成&未完成,并返回参数。 const visibilityFilter = (state = SHOW_ALL, action) => { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } } export default visibilityFilter
- 新建一个
selector/isVisible.js
js// todo项是否可见 方法 import { SHOW_ACTIVE, SHOW_ALL, SHOW_COMPLETED } from '../constants/todo' export function selectVisible(state = [], filter) { switch (filter) { case SHOW_ALL: return state case SHOW_ACTIVE: return state.filter((todo) => !todo.isDone) case SHOW_COMPLETED: return state.filter((todo) => todo.isDone) default: return state } }
- 新建一个
-
在
TodoMain.jsx
中,使用筛选(未完成/已完成/全部)后的状态来循环渲染列表项
javascript
// 筛选出已完成or未完成or全部的项
// 传入两个参数-参数1:所有数据;参数2:过滤条件
const visibleTodos = useSelector((state) =>
selectVisible(state.todos, state.visibilityFilter)
)
二、底部筛选栏设置过滤条件
-
在
TodoFooter.jsx
中,循环渲染过滤条件。 -
给a链接绑定
onClick
事件,触发action
行为。实现数据的过滤展示。js<ul className="filters"> {Object.keys(FILTER_TITLES).map((filterTitle) => ( <li key={filterTitle}> <a href="./#" className={classNames({ selected: filterTitle === filter })} onClick={() => dispatch(setVisibilityFilter(filterTitle))} > {FILTER_TITLES[filterTitle]} </a> </li> ))} </ul>
删除全部已完成☘️
-
给按钮绑定点击事件
onClick
js<button className="clear-completed" onClick={() => dispatch(changeAll(true))} > Clear completed </button>
-
定义一个
action
行为js// 清除所有已完成 export const changeAll = (isDone) => { return { type: CHANGE_ALL, isDone, } }
-
声明
actionTypes
jsexport const CHANGE_ALL = 'todos/changeAll' // 清除所有已完成
-
根据行为在
todosReducer
里面处理状态jscase CHANGE_ALL: return state.filter((item) => { return item.isDone !== action.isDone })
持久化存储 - 本地 🌈
| 💡 将仓库中的状态存储到localStorage中;2. 从浏览器本地存储中得到状态,如果状态存在,仓库中的数据更新为本地存储的数据。
-
定义一个
action
行为js// 本地localstore存储 export const setLocalToken = (todos) => ({ type: SET_LOCAL_TOKEN, todos, })
-
声明
actionTypes
js// 本地localstore存储 export const SET_LOCAL_TOKEN = 'todos/setLocalToken'
-
根据行为在
reducer
里面处理状态jscase SET_LOCAL_TOKEN: return action.todos
-
在
TodoMain.jsx
中触发actionjsconst todos = useSelector((state) => state.todos) // 触发action,传入本地存储的状态 useEffect(() => { const savedTodos = JSON.parse(localStorage.getItem('todos')) if (savedTodos) { dispatch(setLocalToken(savedTodos)) } //[dispatch] 作为依赖数组。只有当 dispatch 更新时才重新执行 useEffect 中的逻辑 }, [dispatch]) // 状态存储到本地 useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)) }, [todos])