总结
一.@reduxjs/toolkit使用
1.1安装
- 安装:
npm install @reduxjs/toolkit
在src目录中的store目录中的index.js中添加
1.2导入包中的对象
- 导入包中的对象
import { createSlice, configureStore } from '@reduxjs/toolkit'
1.3创建切片对象
- 创建切片对象
//这个方法需要传入一个参数是对象,在这个对象中必须要有的是name属性
const counter = createSlice({
//这个名称会作为action.type中的前缀,避免命名冲突,自动追加
name: 'count',
//初始状态数据
initialState: {
num: 10
},
//定义reducers
//定义reducer更新状态数据的函数
//而里面的方法在后期组件中执行dispatch时是作为action函数的函数名去使用的
reducers: {
//需要传入两个参数,一个是state(代理的proxy对象)、一个是action
add(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num += payload;
},
dec(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num -= payload;
}
}
});
1.4获取action函数并导出方法
- 获取action函数并导出方法
export let { add,dec } = counter.actions;
1.5创建store对象
- 创建store对象
//调用rtk的configureStore方法,
//该方法相当于集成了redux和redux-redux的createStore、combineReducers、middleware以及默认支持了Redux DevTools。
const store = configureStore({
reducer: counter.reducer
})
1.6异步使用redux
- 异步使用redux
在store/index.js文件中只需要单独声明一个方法并导出,不需要其他操作
//单独创建一个函数
export let asynctianjia = (payload) => {
return dispatch => {
//执行异步操作
setTimeout(() => {
dispatch(add(payload));
}, 1000)
}
}
1.7导出store
- 导出
export default store;
1.8获取状态数据
- 获取状态数据
console.log(store.getState())
1.9修改状态数据
- 修改状态数据
store.dispatch(add(10))
store.dispatch(dec(5))
1.10store.subscribe()来重新渲染数据
- store.subscribe()来重新渲染数据
不要忘记在主入口中重新渲染组件,因为redux只负责状态管理,不负责组件的重新渲染
import store from './store';
store.subscribe(() => {
root.render(
<>
<App />
</>
);
})
二、react-redux 使用
redux的问题:
需要自己绑定监听来重新渲染
整个组件
每次state更新, 都需要重新render所有组件(使用subscribe方法进行订阅redux状态的更新) ,
效率低,应该有用到state数据的组件需要render
解决: 使用
react-redux
库,想要使用必须下载npm i react-redux
文档: https://react-redux.js.org/
react-redux 是一个 react 插件,用来简化 react 中使用 redux
Provider 组件
:用来包裹整个 React 应用,接收 store 属性,为应用提供 state 和 更新 state 的函数。核心Hook函数
useSelector 函数
: 读取state数据useDispatch 函数
: 得到分发action的dispatch函数
2.1 使用步骤
- 下载
npm i react-redux
- 通过Provider向App及其所有后代组件提供store
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<Provider store={store}>
<App />
</Provider>
</>
);
- 修改Redux.jsx组件中的代码,进行调用Hook函数
import React from 'react'
import { add, dec, asynctianjia } from '../../store/modules/counter';
import { add as a, dec as b } from '../../store/modules/Hit'
import { useSelector, useDispatch } from 'react-redux';
//调用useSelector方法来获取redux中所有的状态数据,redux会将所获取到的状态通过state参数获取(参数名可以自定义)
let { count: { num }, hit: { hitCount } } = useSelector(state => state);
//调用useDispatch进行分发action
let dispatch = useDispatch();
let addCount = () => {
dispatch(add(1)); //省略store
}
... //后面的方法照旧
注意:在回调函数中不能调用React Hook,ReactHook必须在React函数组件或自定义ReactHook函数中调用
React全家桶
前言:
1.组件间的关系
- 父子组件(嵌套组件)
- 兄弟组件(非嵌套组件,平行书写的)
- 祖孙组件 (跨级组件)
2.通信方式
- props
- 消息订阅发布模式:pub/sub
- 集中式状态管理: redux
- 生产者-消费者模式: context
3.具体使用场景
父子组件通信: props
父传子:子组件通过props直接接受。<子组件 name={“张三”}/>
子传父,父组件定义一个函数,把函数传给子组件,
子组件在一定时机触发这个函数,把要传递的数据以函数参数的形式传递过去。
兄弟组件通信:消息订阅与发布、集中式状态管理
祖孙组件通信:集中式状态管理、createContext结合useContext
一、Redux
含义:集中式状态
管理工具,redux只管状态,不管其他(组件的更新)
官网:https://cn.redux.js.org/
2014年 Facebook 提出了 Flux 架构的概念。
Redux 是 JavaScript 应用的可预测状态容器,用来集中管理状态。
特点:集中管理、可预测、易于测试、易于调试、强大的中间件机制满足你所有需求。
注意:redux 是一个独立于 react 的库,可以配合任何 UI 库/框架来使用。
redux的三大原则
-
单一数据源(固定在某一个位置)
-
State是
只读
的(不能直接修改
,必须要借助于store中的dispatch方法
才可以修改) -
使用纯函数来执行产生新 state
什么情况下需要使用redux
- 总体原则: 大型项目状态管理复杂才用,如果是小型项目还是可以使用(useState、props、useContext)
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
Redux有3大核心概念
- Action:操作状态数据的动作
- Reducer:包含状态数据和动作
- Store:仓库
1.1 Redux初体验
- 安装:
npm install @reduxjs/toolkit
在src目录中的store目录中的index.js中添加
- 导入包中的对象
import { createSlice, configureStore } from '@reduxjs/toolkit'
- 创建切片对象
//这个方法需要传入一个参数是对象,在这个对象中必须要有的是name属性
const counter = createSlice({
//这个名称会作为action.type中的前缀,避免命名冲突,自动追加
name: 'count',
//初始状态数据
initialState: {
num: 10
},
//定义reducers
//定义reducer更新状态数据的函数
//而里面的方法在后期组件中执行dispatch时是作为action函数的函数名去使用的
reducers: {
//需要传入两个参数,一个是state(代理的proxy对象)、一个是action
add(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num += payload;
},
dec(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num -= payload;
}
}
});
- 获取action函数并导出方法
export let { add,dec } = counter.actions;
- 创建store对象
//调用rtk的configureStore方法,
//该方法相当于集成了redux和redux-redux的createStore、combineReducers、middleware以及默认支持了Redux DevTools。
const store = configureStore({
reducer: counter.reducer
})
- 获取状态数据
console.log(store.getState())
- 修改状态数据
store.dispatch(add(10))
store.dispatch(dec(5))
- 导出
export default store;
1.2 在组件中使用Redux
- 在Component文件夹中创建Redux目录以及Redux.jsx文件
import React from 'react'
import store, { add, dec } from '../../store/index';
export default function Redux() {
let { num } = store.getState();
let addCount = () => {
store.dispatch(add(1))
}
let decCount = () => {
store.dispatch(dec(1))
}
return (
<div>
<p>点击次数:{num}</p>
<button onClick={addCount}>加1</button>
<button onClick={decCount}>减1</button>
</div>
)
}
- App组件中导入Redux组件
import React from 'react';
import Redux from './Component/Redux/Redux';
export default function App() {
return (
<div>
<Redux />
</div>
)
}
不要忘记在主入口中重新渲染组件,因为redux只负责状态管理,不负责组件的重新渲染
import store from './store';
store.subscribe(() => {
root.render(
<>
<App />
</>
);
})
1.3 在组件中异步使用Redux
- Redux组件中新增一行button,用来执行异步代码功能
<button>异步加20</button>
- 在store/index.js文件中只需要单独声明一个方法并导出,不需要其他操作
//单独创建一个函数
export let asynctianjia = (payload) => {
return dispatch => {
//执行异步操作
setTimeout(() => {
dispatch(add(payload));
}, 1000)
}
}
- Redux组件中导入并使用并传参
import store, { asynctianjia } from '../../store/index';
let asyncaAddCount = () => {
store.dispatch(asynctianjia(20));
}
<button onClick={asyncaAddCount}>异步加20</button>
1.4 给Redux进行分层
- 在store目录中创建modules文件夹,存储每一个切片状态,在其中创建一个counter.js文件
//1、创建reducers
import { createSlice } from '@reduxjs/toolkit'
//2、创建切片对象
//这个方法需要传入一个参数是对象,在这个对象中必须要有的是name属性
const counter = createSlice({
//这个名称会作为action.type中的前缀,避免命名冲突,自动追加
name: 'count',
//初始状态数据
initialState: {
num: 10
},
//定义reducers
//定义reducer更新状态数据的函数
//而里面的方法在后期组件中执行dispatch时是作为action函数的函数名去使用的
reducers: {
//需要传入两个参数,一个是state(代理的proxy对象)、一个是action
add(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num += payload;
},
dec(state, { payload }) {
//输出
// console.log(state);
// console.log(action)
state.num -= payload;
}
}
});
//3、获取action函数并导出方法
export let { add,dec } = counter.actions;
//单独创建一个函数
export let asynctianjia = (payload) => {
return dispatch => {
//执行异步操作
setTimeout(() => {
dispatch(add(payload));
}, 1000)
}
}
//3、导出
export default counter;
- 整理store/index.js文件
import { configureStore } from '@reduxjs/toolkit'
import counter from './modules/counter'
const store = configureStore({ reducer: counter.reducer })
export default store;
- 调整Redux.jsx中文件的导入
import store from '../../store/index';
import { add, dec, asynctianjia } from '../../store/modules/counter';
1.5 在Redux中新增一个状态
需求:新增一个点赞数
- 在store/module文件中新增一个文件,用来存储点赞数状态
import { createSlice } from '@reduxjs/toolkit'
const hit = createSlice({
name: 'hit',
initialState: {
hitCount: 100
},
reducers: {
add(state, { payload }) {
state.hitCount += payload;
},
dec(state, { payload }) {
state.hitCount -= payload;
}
}
})
export let { add, dec } = hit.actions;
export default hit.reducer
- 在store/index.js中导入并合并使用
import { configureStore } from '@reduxjs/toolkit'
import counter from './modules/counter'
import HitReducer from './modules/Hit'
const store = configureStore(
{
reducer: {
count: counter,
hit: HitReducer
}
})
export default store;
- 在Redux组件中导入
import { add, dec, asynctianjia } from '../../store/modules/counter';
//注意:如果声明的方法和其他状态文件中重名,可以使用as添加别名使用
import { add as a, dec as b } from '../../store/modules/Hit'
重新对获取到的状态数据进行解构
let { count: { num }, hit: { hitCount } } = store.getState();
- 增加页面结构
<p>点赞数:{hitCount}</p>
<button onClick={addHit}>增加点赞数</button>
<button onClick={DecHit}>减少点赞数</button>
- 声明函数并调用
let addHit = () => {
store.dispatch(a(1))
}
let DecHit = () => {
store.dispatch(b(1))
}
二、react-redux 使用
redux的问题:
需要自己绑定监听来重新渲染
整个组件
每次state更新, 都需要重新render所有组件(使用subscribe方法进行订阅redux状态的更新) ,
效率低,应该有用到state数据的组件需要render
解决: 使用
react-redux
库,想要使用必须下载npm i react-redux
文档: https://react-redux.js.org/
react-redux 是一个 react 插件,用来简化 react 中使用 redux
Provider 组件
:用来包裹整个 React 应用,接收 store 属性,为应用提供 state 和 更新 state 的函数。核心Hook函数
useSelector 函数
: 读取state数据useDispatch 函数
: 得到分发action的dispatch函数
2.1 使用步骤
- 下载
npm i react-redux
- 通过Provider向App及其所有后代组件提供store
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<Provider store={store}>
<App />
</Provider>
</>
);
- 修改Redux.jsx组件中的代码,进行调用Hook函数
import React from 'react'
import { add, dec, asynctianjia } from '../../store/modules/counter';
import { add as a, dec as b } from '../../store/modules/Hit'
import { useSelector, useDispatch } from 'react-redux';
//调用useSelector方法来获取redux中所有的状态数据,redux会将所获取到的状态通过state参数获取(参数名可以自定义)
let { count: { num }, hit: { hitCount } } = useSelector(state => state);
//调用useDispatch进行分发action
let dispatch = useDispatch();
let addCount = () => {
dispatch(add(1)); //省略store
}
... //后面的方法照旧
注意:在回调函数中不能调用React Hook,ReactHook必须在React函数组件或自定义ReactHook函数中调用
三、利用redux改写Todolist案例
3.1 完成初始状态数据的渲染
- 先整理一个纯的案例,将之前实现的所有代码先删掉
- 在store/modules下创建一个状态文件,名称定位Todo.js
//1、导入
import { createSlice } from '@reduxjs/toolkit';
//2、创建对象
const Todo = createSlice({
name: 'Todo',
initialState: [
{
id: 1,
title: '任务1',
done: false
},
{
id: 2,
title: '任务2',
done: false
}
],
reducers: {
addTo(state, { payload }) {
state.push(payload);
}
}
});
export let { addTo } = Todo.actions;
//3、暴露
export default Todo.reducer;
- 在store/index.js文件中导入
import { configureStore } from '@reduxjs/toolkit'
import TodoReducer from './modules/Todo'
const store = configureStore(
{
reducer: {
todolist: TodoReducer
}
})
export default store;
- 修改TodoItem.jsx文件,获取状态数据并渲染读取
import React from 'react'
import { useSelector } from 'react-redux'
export default function TodoItem() {
let { todolist } = useSelector(state => state);
return (
<div>
{
todolist.map(item => {
return <li key={item.id}>
<label>
<input type="checkbox" />
<span>{item.title}</span>
</label>
<button className="btn btn-danger" >删除</button>
</li>
})
}
</div>
)
}
3.2 新增一个任务
直接修改TodoHeader.jsx文件中的内容
import React, { useState } from 'react'
import './TodoHeader.css'
import { useDispatch } from 'react-redux'
import { addTo } from '../../../store/modules/Todo'
import { nanoid } from 'nanoid'
export default function TodoHeader() {
//可以使用受控组件或者是非受控组件写法来获取文本框中的数据
let [title, setTitle] = useState('');
let dispatch = useDispatch();
let add = (e) => {
setTitle(e.target.value);
}
let keyupFun = (e) => {
//判断键盘按键是否是回车键
if (e.keyCode === 13) {
//将任务直接添加到状态中
dispatch(addTo({
id: nanoid(),
title,
done: false
}))
//清空文本框
setTitle('');
}
}
return (
<div className="todo-header">
<input type="text" value={title} placeholder="请输入你的任务名称,按回车键确认"
onChange={add} onKeyUp={keyupFun}
/>
</div>
)
}
3.3 点击复选框修改某一个任务的状态
- 现在store/modules/Todo.js文件中新增一个方法
//点击复选框转换状态 {id:xxx,done:xx}
checkTodo(state, { payload }) {
//思路:循环找和id一样的任务做状态的修改
state.map(item => {
if (item.id == payload.id) {
item.done = payload.done;
}
})
}
不要忘记导出
export let { addTo, checkTodo } = Todo.actions;
- 修改TodoItem.jsx
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { checkTodo } from '../../../store/modules/Todo'
export default function TodoItem() {
let { todolist } = useSelector(state => state);
let dispatch = useDispatch();
let changeDone = (id, e) => {
//修改状态
dispatch(checkTodo({ id, done: e.target.checked }));
}
return (
<div>
{
todolist.map(item => {
return <li key={item.id}>
<label>
<input type="checkbox" checked={item.done} onChange={e => changeDone(item.id, e)} />
<span className={item.done ? 'done' : ''}>{item.title}</span>
</label>
<button className="btn btn-danger" >删除</button>
</li>
})
}
</div>
)
}
3.4 删除某一个任务
- 在store/modules/Todo.js中新增一个删除单条的方法
//声明一个可以删除单条任务的方法
deleteOneTodo(state, { payload }) {
//思路:循环状态数据只要是符合传递的id那一条删掉
state.forEach((item, index) => {
if (item.id == payload) {
state.splice(index, 1);
}
})
}
不要忘记导出
export let { deleteOneTodo } = Todo.actions;
- 在TodoItem.jsx文件中导入
import { deleteOneTodo } from '../../../store/modules/Todo'
- 给删除按钮添加点击事件并传递id参数
<button className="btn btn-danger" onClick={e => deleteTodo(item.id)}>删除</button>
- 点击点击方法
let deleteTodo = (id) => {
//删除
dispatch(deleteOneTodo(id))
}
3.5 实现所有任务的全选与反选
- 在store/modules/Todo.js中新增一个控制全选与反选的方法
//声明一个可以实现全选与反选的方法 payload只需要全选框的选中状态值,要么true,要么false
checkAllTodo(state, { payload }) {
//思路:给每一个任务的状态都修改成payload中传入的状态值
state.map(item => {
item.done = payload
})
}
不要忘记导出
export let { checkAllTodo } = Todo.actions;
- 在TodoFooter.jsx文件中导入
import { useDispatch, useSelector } from 'react-redux'
import { checkAllTodo } from '../../../store/modules/Todo'
- 给复选框添加事件函数
<input type="checkbox" onChange={checkAll} checked={
Todolist.every(item => item.done) && Todolist.length > 0
} />
- 声明函数,修改状态
let { Todolist } = useSelector(state => state);
let dispatch = useDispatch();
let checkAll = (e) => {
dispatch(checkAllTodo(e.target.checked));
}
3.6 统计任务数量
- 在TodoFooter.jsx文件中导入useSelector
import { useSelector } from 'react-redux'
- 调用获取todolist中的数据
let { todolist } = useSelector(state => state);
- 读取数据
<span>已完成{todolist.filter(item => item.done).length}</span> / 全部{todolist.length}
3.7 清除已完成任务
- 在store/modules/Todo.js中新增一个方法
//声明一个清除所有已完成的方法
clearAllTodo(state) {
//思路:循环所有的任务,只要是选中状态的,就删除
for (let i = 0; i < state.length; i++) {
if (state[i].done) {
state.splice(i, 1);
i--;
}
}
}
不要忘记导出
export let { clearAllTodo } = Todo.actions;
- 在TodoFooter.jsx文件中导入
import { clearAllTodo } from '../../../store/modules/Todo'
- 给按钮添加点击事件
<button className="btn btn-danger" onClick={clearAll}>清除已完成任务</button>
- 声明函数,修改状态数据
let clearAll = () => {
dispatch(clearAllTodo());
}
3.8 异步实现获取任务状态数据
- 利用json-server启动服务
json-server --watch(-w) db.json --port(-p) 3001
- 清空初始状态数据(因为要来自于服务中)
initialState: []
- 在store/modules/Todo.js中新增方法
//添加方法
setTodo(state, { payload }) {
//通过push方法向状态数据进行追加
state.push(...payload);
}
在定义一个异步处理的方法
import axios from '../../Component/utils/http'
export let aysncgetTodo = () => {
return async dispatch => {
//添加异步任务
let result = await axios.get('/todos');
//更新状态数据
dispatch(setTodo(result.data))
}
}
- 在TodoList.jsx文件中导入
import React, { useEffect } from 'react'
import { aysncgetTodo } from '../../store/modules/Todo'
import { useDispatch } from 'react-redux'
let dispatch = useDispatch();
useEffect(() => {
dispatch(aysncgetTodo());
}, [])
3.9 异步实现新增任务状态数据
- 在store/modules/Todo.js中新增方法
export let asyncaddTodo = (payload) => {
return async dispatch => {
//添加异步任务
let result = await axios.post('/todos', payload);
//更新状态数据
dispatch(addTo(result.data))
}
}
- 在TodoHeader.jsx组件中的addTodo函数导入替换成asyncaddTodo
import { asyncaddTodo } from '../../../store/modules/Todo'
将函数的调用也替换
let keyupFun = (e) => {
//判断键盘按键是否是回车键
if (e.keyCode === 13) {
//将任务直接添加到状态中
dispatch(asyncaddTodo({
id: nanoid(),
title,
done: false
}))
//清空文本框
setTitle('');
}
}
3.10 异步实现更新单条任务状态数据
- 在store/modules/Todo.js中新增方法
export let asyncUpdateTodo = (payload) => {
return async dispatch => {
//添加异步任务
let result = await axios.patch(`/todos/${payload.id}`, { done: payload.done })
//更新状态数据
dispatch(checkTodo(result.data))
}
}
- 在TodoItem.jsx组件中的checkTodo函数导入替换成asyncUpdateTodo
import { deleteOneTodo, asyncUpdateTodo } from '../../../store/modules/Todo'
将函数的调用也替换
let changeDone = (id, e) => {
//修改状态
dispatch(asyncUpdateTodo({ id, done: e.target.checked }));
}
3.11 异步实现删除单条任务状态数据
- 在store/modules/Todo.js中新增方法
export let asyncdeleteTodo = (payload) => {
return async dispatch => {
//添加异步任务
let result = await axios.delete(`/todos/${payload}`)
console.log(result)
//更新状态数据
dispatch(deleteOneTodo(payload))
}
}
- 在TodoItem.jsx组件中的deleteOneTodo函数导入替换成asyncdeleteTodo
import { asyncdeleteTodo, asyncUpdateTodo } from '../../../store/modules/Todo'
将函数的调用也替换
let deleteTodo = (id) => {
//删除
dispatch(asyncdeleteTodo(id))
}
四、Redux梳理总结
- action
- 本身就是一个普通对象
{type: 'add', payload: 任意类型的值}
- 本身就是一个普通对象
- reducer
- 本身是一个函数. 加工厂函数, 与原生的 reduce 函数功能很像
- 作用: 就是根据 action 对象对保存的状态进行更新
- store
- 仓库对象
- dispatch
- 本身是一个函数. 是属于 store 对象的方法
- 作用: 分发任务,
- 参数: action 对象
- dispatch({type: ‘add’, payload: 1});
- dispatch(函数类型的值) => 异步的状态更新