About 大家好,我是且陶陶,今天跟大家分享一个redux的todoList案例,通过这个案例能够快速掌握redux的基本知识点🌹
❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…❤️…
最流行的状态管理工具之一。(类似于 vue中的vuex)
Redux和React是两个独立的工具/
store分配要做的事action
给reducer
代码地址🍻: TodoMvc 欢迎大家批评指正~
🍦 添加事项 🍦 删除事项 🍦 完成or未完成事项 🍦 全选反选 🍦 清空
在 store/reducer/todos.js
中处理行为
const 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并导出
// 模块合并 并导出
import todos from './todo'
import { combineReducers } from 'redux'
const rootReducer = combineReducers({ todos })
export default rootReducer
在store/index.js
中挂载 reducer和action
// 创建仓库,挂载reducers 并导出
import { createStore } from 'redux'
import reducers from './reducers/index'
// 创建store
const store = createStore(reducers)
export default store
在index.jsx
中,引入redux
和react-redux
用Provider包裹根组件,并提供store值
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 操作状态。
import 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()
...
...
步骤
TodoMain.jsx
中。循环渲染todolist
中的每一项。传递每一项item ...
...
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
子组件中接收每一项。并渲染
export 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
事件
<input
className="toggle"
type="checkbox"
checked={todoitem.isDone}
onChange={() => {
dispatch(changeDone(todoitem.id, !todoitem.isDone))
}}
/>
定义action行为
import { CHANGE_STATE } from '../constants/todo'
// 修改单个状态的行为
export const changeDone = (id) => {
return {
type: CHANGE_STATE,
id,
}
}
声明actionType
// 声明 constantTypes
export const CHANGE_STATE = 'todos/changeDone' // 修改单个复选框状态类型
todosReducer
里面处理状态
case CHANGE_STATE:
// 注意:状态不可变
return state.map((item) => {
if (item.id === action.id) {
return {
...item,
isDone: action.isDone,
}
} else {
return item
}
})
使用dispatch
触发action
import 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,然后过滤掉它。
思路:
onClick
action
行为actionTypes
todosReducer
里面处理状态代码:
给X绑定点击事件 onClick
<button
className="destroy"
onClick={() => {
dispatch(delTodo(todoitem.id))
}}
></button>
定义一个action
行为
// 删除单个代办项
export const delTodo = (id) => {
return {
type: DELETE_TODO,
id,
}
}
声明actionTypes
export const DELETE_TODO = 'todos/delTodo' // 删除单个待办
根据行为在todosReducer
里面处理状态
case DELETE_TODO:
return state.filter((item) => {
// 过滤掉与选择的这一行相同的id
return item.id !== action.id
})
💡 首先对拿到的做非空校验;然后数组添加一项数据。
绑定onChange
事件,得到输入框的输入内容
import 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
<input
className="new-todo"
placeholder="今天做什么?"
value={inputValue}
autoFocus
onChange={addValue}
onKeyDown={(e) => {
if (e.key === 'Enter') {
console.log('回车', inputValue)
dispatch(addTodo(inputValue))
setInputValue('') // 清空输入框
}
}}
/>
定义一个action
行为
// 添加单个待办项
export const addTodo = (inputValue) => {
return {
type: ADD_TODO,
name: inputValue,
}
}
声明actionTypes
export const ADD_TODO = 'todos/addTodo' // 添加单个待办项
根据行为在todosReducer
里面处理状态
case ADD_TODO:
if (!action.name.trim()) return
// 状态不可变!!!
return [
{
id: state.length + 1,
name: action.name,
isDone: false,
},
...state,
]
<aside>
💡 要实现底部筛选,可以在footer中使用过滤器进行分发。
</aside>
声明actionTypes
// 筛选栏标题
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'
定义筛选栏标签的静态数据
import { SHOW_ALL,SHOW_ACTIVE,SHOW_COMPLETED } from "./todo";
export const FILTER_TITLES = {
[SHOW_ALL]: 'All',
[SHOW_ACTIVE]: 'Active',
[SHOW_COMPLETED]: 'Completed'
}
定义一个action
行为
// 底部筛选栏 - 用于更新Redux store中的过滤状态
export const setVisibilityFilter = (filter) => ({
type: SET_VISIBILITY_FILTER,
filter
})
根据行为在todosReducer
里面处理状态
reducer/filter.js
import { 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
// 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
中,使用筛选(未完成/已完成/全部)后的状态来循环渲染列表项
// 筛选出已完成or未完成or全部的项
// 传入两个参数-参数1:所有数据;参数2:过滤条件
const visibleTodos = useSelector((state) =>
selectVisible(state.todos, state.visibilityFilter)
)
在TodoFooter.jsx
中,循环渲染过滤条件。
给a链接绑定onClick
事件,触发action
行为。实现数据的过滤展示。
<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
<button
className="clear-completed"
onClick={() => dispatch(changeAll(true))}
>
Clear completed
</button>
定义一个action
行为
// 清除所有已完成
export const changeAll = (isDone) => {
return {
type: CHANGE_ALL,
isDone,
}
}
声明actionTypes
export const CHANGE_ALL = 'todos/changeAll' // 清除所有已完成
根据行为在todosReducer
里面处理状态
case CHANGE_ALL:
return state.filter((item) => {
return item.isDone !== action.isDone
})
💡 将仓库中的状态存储到localStorage中;2. 从浏览器本地存储中得到状态,如果状态存在,仓库中的数据更新为本地存储的数据。
定义一个action
行为
// 本地localstore存储
export const setLocalToken = (todos) => ({
type: SET_LOCAL_TOKEN,
todos,
})
声明actionTypes
// 本地localstore存储
export const SET_LOCAL_TOKEN = 'todos/setLocalToken'
根据行为在reducer
里面处理状态
case SET_LOCAL_TOKEN:
return action.todos
在TodoMain.jsx
中触发action
const 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])