六、Redux
1.Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
1.操作步骤
1.下载安装
yarn add redux2.创建仓库
reducer:仓库管理员,函数形式,有两个参数state、action
- state:用来存放数据
- action:是个对象,有两个参数,type、payload
- type:一般是字符串,根据传过来的字符串触发不同方法
- payload:调用action里的方法时传过来的参数,可以是任意类型
src/store/index.js
const initData={
num:1
}
const reducer = (state=initData,action)=>{
//...
return state
}
const store = Redux.createStore(reducer)3.获取数据
import store from '路径'
store.getState().键4、更新数据/触发动作
- type 选择触发的方法
- payload 传递的参数 可选
store.dispatch({
type: 'ADD/DEL',
payload: 参数任意类型
})5.监控仓库数据变化
store.subscribe(() => {
组件state中的键=store.getState().键
})2.模块化
语法
注意:模块中的方法名必须唯一
// A模块
const initData1={
num:1
}
const modeA=(state=initData1,action)=>{
//...
return state
}
// B模块
const initData2={
num:2
}
const modeB=(state=initData1,action)=>{
//...
return state
}
const store = Redux.createStore(
Redux.combineReducers({
modeA,
modeB
})
);
// 获取数据
console.log(store.getState().modeA.键)
console.log(store.getState().modeB.键)
// 触发更新
store.dispatch({
type:'XXX',
payload:xxx
})
// 监控仓库数据变化
store.subscribe(() => {
组件state中的键=store.getState().键
})2.高阶组件
1.高阶函数
参数为函数或者返回函数的函数,例如forEach、map、filter
2.高阶组件
概念:本身是一个函数,形参是组件,必须返回新组件
好处:减少代码冗余,方便后期维护
场景:网站公共footer
语法:
higerFooter.jsx
import React from "react";
const higherFooter = (OldComponent) => {
return () => {
return (
// 空标签不会占空间
<>
<OldComponent />
<footer style={{ background: "gray" }} >
网站footer:xxxxxxx
</footer>
</>
);
};
};
export default higherFooter;调用:
home.jsx
import higherFooter from './higherFooter'
const Home=()=>{
return (
<h1>主页</h1>
)
}
export default higherFooter(Home)3.immutable
1.概括
浅拷贝:减少内存占用,但是容易相对出问题
深拷贝:解决浅拷贝问题,但是增加内存的占用
解决:使用Facebook的immutable技术实现深拷贝
首先有一个immutable对象
然后只要修改了就会返回一个新的immutable对象,它由两部分组成
1、没有改的数据 旧内存地址
2、修改后的数据 新内存地址2.语法
1.创建immutable对象
Immutable.fromJS()方法传入一个普通对象,创建一个新immutable对象
let obj={
a:1,
b:[2,3]
}
let immutable对象 = Immutable.fromJS(obj)2.修改
let 新的immutable对象 = list.setIn([键1,....,键n],数据)3.修改多个
let 新的immutable对象 = list.updateIn([键1,....,键n],回调 当前数据=>当前数据+1)4.获取
let 新的immutable对象 = list.getIn([键1,....,键n])5.immutable对象转换为普通js对象
新的immutable对象.toJS()4.react-redux
基于redux、context封装
1.相比较redux
redux问题1:层层传 或 每个组件中都需要些../../store
redux问题2:每个组件都要监控
react-redux通过:context解决层层传 或 每个组件都需要导入问题
react-redux通过:高阶组件将store里面的数据注入props中
2.语法
1.安装redux、react-redux
yarn add redux react-redux2.修改入口文件
src/index.js
import { createRoot } from "react-dom/client";
// 1.导入 Provider
import { Provider } from "react-redux";
// 2.导入store
import store from './store'
import App from "./App";
const root = createRoot(document.querySelector("#root"));
// 3.Provider包裹根组件
root.render(
<Provider store={store}>
<App />
</Provider>
);3.定义store
src/store/index.ts
import {legacy_createStore as createStore,combineReducers} from 'redux'
import test from '../pages/test/store'
export default createStore(
combineReducers({
test,
})
)4.定义reducer
src/pages/test/store/index.ts
const initData={
num:11
}
const reducer=(state=initData,action)=>{
// 深拷贝
state=JSON.parse(JSON.stringify(state))
switch (action.type) {
case 'ADD':
state.num++
break;
default:
break;
}
return state
}
export default reducer5.组件中
从
react-redux导入connect导出组件时调用
connect方法,并传入mapStateToProps,mapDispatchToProps两个参数定义
mapStateToProps,state作为参数- mapStateToProps用来定义数据
- mapStateToProps中return的数据会被注入到当前组件的props中
- 可以通过
state.模块名.键获取store中state里的数据
定义
mapDispatchToProps,dispatch作为参数- mapDispatchToProps用来定义方法
- mapDispatchToProps中return的方法同样会被注入到当前组件的props中
src/pages/test//index.jsx
import {connect} from 'react-redux'
const 组件名=()=>{
const {a,data,handleAdd}=this.props
return(
<ul>
<li>{a}</li>
<li>
{num}
<button onClick={()=>handleAdd()}>触发store中事件</button>
</li>
</ul>
)
}
// 将state放到props属性中
const mapStateToProps = state => {
return {
a:11,
num:state.test.num
}
}
// 将dispatch封装成方法放到props属性中
const mapDispatchToProps = dispatch => {
return {
handleAdd:()=>{
dispatch({
type:'ADD'
})
}
}
}
// 1-从react-redux导出connect
// 2-调用connect传递两个实参都是函数类型 这两个函数的返回值 会注入到当前组件的props上
// 3-组件中通过属性使用模型数据
export default connect(mapStateToProps, mapDispatchToProps)(组件名)5.redux-immutable
明确:redux-immutable是基于immutable封装让你在redux中使用immutable
需求:将之前写的购物车手动深拷贝改为immutable深拷贝提升性能
1.配置(仅一次)
1.下载安装
yarn add immutable redux-immutable2.配置store
注意:combineReducers换成从redux-immutable导入
src/store/index.js
// 导入模块
import { createStore } from "redux";
import { combineReducers } from "redux-immutable"; // 改
import { composeWithDevTools } from "redux-devtools-extension";
// 导入管理员reducer
import carts from "../pages/carts/store";
import orders from "../pages/orders/store";
// 导出仓库
export default createStore(
combineReducers({
carts,
orders,
}),
composeWithDevTools()
);3.配置reducer
- 从
immutable导入fromJS - 数据对象用
fromJS()包裹 - 修改数据用
updateIn()
// 导入模块
import { fromJS } from "immutable";
// 定义reducer
let initData = fromJS({
a: 1,
carts: [
{ id: 1, title: "商品1", num: 1, price: 1.11 },
{ id: 2, title: "商品2", num: 2, price: 2.22 },
{ id: 3, title: "商品3", num: 3, price: 3.33 },
],
});
// 纯函数 就是里面不要写任何副作用代码(副作用:异步请求、H5存储等)
const reducer = (state = initData, action) => {
// 深拷贝:
// let temp = JSON.parse(JSON.stringify(state));
switch (action.type) {
case "ADD":
// temp.carts[action.payload].num++;
return state.updateIn(["carts", action.payload, "num"], (val) => val + 1);
break;
case "DEL":
// temp.carts[action.payload].num--;
return state.updateIn(["carts", action.payload, "num"], (val) => val - 1);
// break;
default:
break;
}
// return temp;
return state;
};
export default reducer;4.组件中使用
- 用
toJS()把immutable对象转换成普通对象使用
// 将state放到props属性中
const mapStateToProps = state => {
// console.log(state) //就是仓库数据
return {
carts: state.toJS().carts.datas
}
}
// 将dispatch封装成方法放到props属性中
const mapDispatchToProps = dispatch => {
return {
add: (index) => dispatch({type:'INCRMENT', payload: index}),
del: (index) => dispatch({type:'DECRMENT', payload: index}),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Index)6.中间件
概念
用来处理中间逻辑的称为中间件
场景
判断用户是否登录
1.redux-logger
用来打印变化的数据
下载
yarn add redux-logger配置
src/store/index.js
- 从
redux-logger导入logger - 导出
createStore时,传入第二个参数applyMiddleware(thunk, logger)
// 导入模块
import { createStore, applyMiddleware } from "redux";
import { combineReducers } from "redux-immutable";
import thunk from "redux-thunk";
import logger from "redux-logger";
// 导入管理员reducer
import carts from "../pages/carts/store";
// 导出仓库
export default createStore(
combineReducers({
carts,
}),
applyMiddleware(thunk, logger)
);2.redux-thunk
redux-thunk是redux一个中间件,用于解决异步问题
下载
yarn add redux-thunk配置
// import {createStore, combineReducers} from 'redux'
// import {createStore} from 'redux' // 更新
import {createStore, applyMiddleware} from 'redux' // 更新2(本次1)
import thunk from 'redux-thunk' // 新增.(本次2)
// 原因:reducer里面的数据使用了immutable语法
// 以前:从redux导出的combineReducers能力不够
import {combineReducers} from 'redux-immutable'; // 新增,
import {composeWithDevTools} from "redux-devtools-extension"
// 2. 导入reducers(实战:1都放在src/store目录下 学习,2放到pages目录中 项目)
import carts from './carts/index'
import orders from './orders/index'
// 3. 合并导出
// export default createStore(cartReducer)
// export default createStore(cartReducer, composeWithDevTools())
export default createStore(
combineReducers({
carts,
orders,
}),
composeWithDevTools(applyMiddleware(thunk))
) // 更新(本次3)使用
export const postCartAction = index => {
return dispatch => {
// 异步请求代码
setTimeout(() => { // 异步请求axios、fetch都是会发异步请求
dispatch({
type:'INCRMENT',
payload: index
})
}, 2000)
}
}3.redux-saga
安装
yarn add redux-sagaredux-saga 是 redux 一个中间件,用于解决异步问题
store/index.js
import { createStore, applyMiddleware } from "redux";
import { combineReducers } from "redux-immutable";
import thunk from "redux-thunk";
import createSagaMiddleware from "redux-saga"; // 改1.1
import carts from "../pages/carts/store";
import rootSagas from "./sagas"; // 改2.1
const sagaMiddleware = createSagaMiddleware(); // 改1.2
export default createStore(
combineReducers({
carts,
}),
applyMiddleware(thunk, sagaMiddleware) // 改1.3
);
sagaMiddleware.run(rootSagas); // 改2.2store/sagas.js 后期所有的子saga都得导入注册
import { takeEvery } from "redux-saga/effects";
// 注入
import cartsSaga from "../pages/carts/store/saga";
/* eslint require-yield:0 */
const example = {
namespace: "example",
// 副作用:写异步请求
effects: {
// 获取话题列表
*helloAsync({ payload }) {
console.log("hello saga");
},
// *helloAsync({ payload }) {
// console.log("hello saga");
// // const res = yield call(api目录下方法名切记不能加小括号, 接口参数);
// // yield put({ type: '动作', payload: '参数' });
// 咱们这:用来触发 redux模块化里面reducer
// 后期阿里项目中:dva 用来namespace、effects同级reducer
// },
},
};
export default function* rootSagas() {
// 后期所有的saga代码都必须在这边注册下
yield takeEvery(
`${example.namespace}/helloAsync`,
example.effects.helloAsync
);
yield takeEvery(
`${cartsSaga.namespace}/addAsync`,
cartsSaga.effects.addAsync
);
}触发example
dispatch({ type: 'namespace名称/effects名称', payload: 参数 })
7.redux-toolkit
1.配置store
src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
// 导入模块
import test1 from "../pages/test1/store";
const store = configureStore({
reducer: {
test1,
},
});
// 额外
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
export default store;2.定义数据
- name:模块名
- initialState:定义数据
- reducers:定义方法,里面的方法具有两个参数:state,action
- state:通过
state.键获取数据 - action:可通过
action.payload获取传过来的参数
- state:通过
src/pages/test1/store/index.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
const slice = createSlice({
// 命名空间作用
// 获取 =》 state.模块名.initialState键
// 更新 =》 dispatch({type: '模块名/reducers中的方法名'}) 或 dispatch(incr(参数))
name: "test1",
initialState: {
count: 1,
} as Test1.State,
reducers: {
incr(state, action: PayloadAction<number>) {
state.count += action.payload;
},
},
});
// 导出方法
export const { incr } = slice.actions; // actions结构reducers数据
export default slice.reducer;3.入口文件注册store
src/index.tsx
import { createRoot } from "react-dom/client";
import App from "./App";
// 导入store
import { Provider } from "react-redux";
import store from "./store";
const root = createRoot(document.querySelector("#root") as HTMLElement);
// 渲染
root.render(
<Provider store={store}>
<App />
</Provider>
);4.同步数据
1.导入useSelector,useDispatch
import {useSelector, useDispatch} from 'react-redux'2.获取数据
const 键 = useSelector(state=>state.模块名.键)3.调用方法
const dispatch = useDispatch()
dispatch({
type: 'test1/incr'
payload: 2
})例子
import { FC } from "react";
import { RootState } from "../../store";
import { incr } from "./store";
import { useSelector, useDispatch } from "react-redux";
interface Props {}
const Test1: FC<Props> = () => {
const count = useSelector((state: RootState) => state.test1.count);
const dispatch = useDispatch();
return (
<fieldset>
<legend>Test1</legend>
<h1>count: {count}</h1>
<button onClick={() => dispatch({ type: "test1/incr", payload: 1 })}>+1</button>
<button onClick={() => dispatch({ type: "test1/incr", payload: 2 })}>+2</button>
<button onClick={() => dispatch(incr(3))}>+3 直接调用</button>
</fieldset>
);
};
export default Test1;5.异步任务
src/pages/test1/store/index.ts
// # 异步请求
// 1-创建异步任务:createAsyncThunk方法
// 2-组件调用
// - 2.1 组件直接保存数据
// - 2.2 通过仓库 :配置额外的reducer 也就是 extraReducers 这键 来保存数据
import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit";
// 1-定义异步任务
// export const 功能名Async = createAsyncThunk("当前模块名/当前异步请求方法名", async () => {
export const countAsync = createAsyncThunk("test1/countAsync", async () => {
const res = await fetch("https://www.baidu.com/sugrec?prod=pc&wd=htm").then(
(res) => res.json()
);
return res;
});
const slice = createSlice({
// 命名空间作用
// 获取 =》 state.模块名.initialState键
// 更新 =》 dispatch({type: '模块名/reducers中的方法名'}) 或 dispatch(incr(参数))
name: "test1",
initialState: {
count: 1,
baiduLoading: false,
baidu: [],
} as Test1.State,
reducers: {
incr(state, action: PayloadAction<number>) {
state.count += action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(countAsync.pending, (state) => {
state.baiduLoading = true;
})
.addCase(countAsync.fulfilled, (state, action) => {
state.baiduLoading = false;
state.baidu = action.payload.g;
// console.log("监控countAsync成功了!!!", action.payload.g);
})
.addCase(countAsync.rejected, (state) => {
state.baiduLoading = false;
});
},
});
export const { incr } = slice.actions; // actions结构reducers数据
export default slice.reducer;types.d.ts
declare namespace Test1 {
interface BaiduItem {
q: string;
[key: stirng]: any;
}
interface State {
count: number;
baiduLoading: boolean;
baidu: BaiduItem[];
}
}组件内调用
import { FC, useEffect } from "react";
import { RootState, useAppDispatch } from "../../store";
// import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { incr, countAsync } from "./store";
interface Props {}
const Test1: FC<Props> = () => {
// const count = useSelector((state: Test1.State) => {
// const count = useSelector((state: { test1: Test1.State }) => {
// return state.test1.count;
// });
const count = useSelector((state: RootState) => state.test1.count);
const baiduLoading = useSelector(
(state: RootState) => state.test1.baiduLoading
);
const baidu = useSelector((state: RootState) => state.test1.baidu);
// const dispatch = useDispatch(); // 默认dispatch不可以执行异步任务 除非你写jsx代码 现在是tsx代码
const dispatch = useAppDispatch();
// 触发异步任务
useEffect(() => {
// 请求已发送 =》分析f12发送请求了
// dispatch(countAsync());
// 拿数据方法1
// dispatch(countAsync()).then((res) => {
// console.log("有接口数据:", res);
// console.log("有接口数据-payload:", res.payload.g); // 保存到useState中去展示
// });
// 拿数据方法2 - 还是得改仓库
dispatch(countAsync());
}, [dispatch]);
return (
<fieldset>
<legend>Test1</legend>
<h1>count: {count} </h1>
<button
onClick={() =>
dispatch({
type: "test1/incr",
payload: 1,
})
}
>
count+1
</button>
<button
onClick={() =>
dispatch({
type: "test1/incr",
payload: 2,
})
}
>
count+2
</button>
<button onClick={() => dispatch(incr(3))}>count+3</button>
<h1>度娘数据</h1>
<ul>
{baiduLoading ? (
<li>
<h1>loading....</h1>
</li>
) : (
baidu.map((item: Test1.BaiduItem, i: number) => {
return <li key={i}>{item.q}</li>;
})
)}
</ul>
</fieldset>
);
};
export default Test1;