图解
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/254e10fd2a3bcd4a6079ecd4ab507e50.png#pic_center)
1. redux
npm install redux --save
1.1 概述与使用原则
- 是一个专门用来做
状态管理的js库
(不是react插件) - 作用:集中式管理react应用中的多个组件共享的状态
- 使用:
- 某个组件的状态,需要让其他组件可以随时拿到
(共享)
- 一个组件需要改变另一个组件的状态
(通信)
- 某个组件的状态,需要让其他组件可以随时拿到
1.2工作流程
redux工作流程图如下:
1.2.1 三个核心
- action
- 动作的对象
- 包含两个属性
type
:属性标识,值为字符串,唯一,必要属性data
:数据属性,值任意类型,可选属性- 例子
{type:'CHANGE_NAME', data: {name: 'why'}}
- reducer
- 用于初始化、加工状态
- 加工时,根据旧的state和action,产生新的state的
纯函数
- 例子
if (type === 'CHANGE_NAME') {return { ...state, name }}
store
- 将
state
、action
、reducer
联系在一起的对象 - 加工时,根据旧的
state和action
,产生新的state的纯函数
1.3 store
- 整个文件以modules划分
│ └─ store
│ ├─ actions // actions,文件夹内以模块区分
│ │ ├─ count.js
│ │ └─ person.js
│ ├─ constants.js // action type唯一标识常量
│ ├─ index.js // 入口文件
│ └─ reducer // reducer,文件夹内以模块区分
│ ├─ conut.js
│ ├─ index.js // reducer统一暴露文件,合并reducers
│ └─ persons.js
- 引入
createStore
,专门用于创建redux中最为核心的store对象,而redux-thunk
、applyMiddleware用于支持异步action,
npm i redux-thunk
// src/store/index.js
import { createStore, applyMiddleware } from "redux";
// 用于支持异步action
import thunk from "redux-thunk";
import reducers from "./reducers";
export default createStore(reducers, applyMiddleware(thunk));
1.4 action
- 定义action对象中
type类型
的常量值
// src/store/constants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
- 创建action,action返回
对象
,异步action返回函数
可用于发送网络请求,执行setTimeout
等,
// src/store/actions/count.js
import { INCREMENT, DECREMENT } from "../constants";
// 普通action的值为object `{type: INCREMENT, data }`
export const increment = data => ({ type: INCREMENT, data });
export const decrement = data => ({ type: DECREMENT, data });
export const incrementAsync = (data) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data));
}, 500);
};
};
异步action
- 延迟的动作不想交给组件本身,想交给action
- 执行异步方法对状态进行操作,但是具体的数据需要靠异步任务返回
1.5 reducer
- reducer函数会接到两个参数,分别为:之前的状态
(state)
,动作对象(action)
- 从action对象中获取
type
,data
- 根据
type
决定如何加工数据 reducer
没有初始化值时为undefined
因此我们可以设置初始值initialState
import {INCREMENT, DECREMENT} from '../constants'
// 初始化状态
const initialState= 0;
export default function count(state = initialState, action) {
const { type, data } = action;
switch (type) {
case INCREMENT:
return state + data;
case DECREMENT:
return state - data;
default:
return state;
}
}
注意
-
state只读
- 唯一修改State的方法一定是触发
action
,不要试图在其他地方通过任何的方式来修改State:这样可以保证所有的修改都被集中化处理
,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
- 唯一修改State的方法一定是触发
-
使用
纯函数
来执行修改- 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State:
- 随着应用程序的复杂度增加,我们可以将
reducer拆分成多个小的reducers
,分别操作; - 但是所有的reducer都应该是
纯函数
,不能产生任何的副作用
;
1.5.1 合并reducers
通过combineReducers
合并,接收的参数是一个对象,对象的key值与getState()得到的对象的key一致
// src/store/reducers/index.js
import { combineReducers } from "redux";
import count from "./conut";
import persons from "./persons";
export default combineReducers({
count,
persons,
});
1.6 dispatch getState 及状态更新
- 组件通过
getState()
拿store的数据 dispatch
触发action- subscribe() 完成视图更新
import store from "../../store";
import { increment } from "../../store/action/count";
//redux内部不支持自动更新,需要通过subscribeAPI监听redux中状态变化,只有变化,就需要重新调用render
componentDidMount() {
store.subscribe(() => {
this.forceUpdate();
});
}
clickIncrement = () => {
store.dispatch(increment(+1));
};
render() {
return (
<div>
<h1>当前求和为: {store.getState()}</h1>
...
<button onClick={this.clickIncrement}>+</button>
</div>
)
}
2. react-redux
2.1 基本特征
- redux需要监听store变化更新视图,利用
store.subscribe(() => { this.forceUpdate(); })
;react-redux不需要监听 - react-redux将组件分为
UI组件
、容器组件
;redux的操作都在容器组件中, - 单一职责原则;通过
connect(mapStateToProps, mapDispatchToProps)(UI)
连接容器组件与UI组件;redux没有区分 - UI组件负责
UI的呈现
,容器组件负责管理数据和逻辑
,如果一个组件既有UI又有业务逻辑,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图
2.2 connect()、mapStateToProps
React-Redux
提供connect
方法,用于从UI组件
生成容器组件
- 下面代码中,
CountUI
是UI组件
,利用connect
最后导出的是容器组件
- 为了定义业务逻辑,需要给出下面两方面的信息:
输入逻辑:外部的数据(即
state对象
)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI组件传出去
connect
方法接受两个参数:mapStateToProps
和mapDispatchToProps
。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state
映射到 UI 组件的参数(props
),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action
mapStateToProps
接收 state
参数,mapDispatchToProps
接收 dispatch
参数
// 容器组件
import { connect } from "react-redux";
import CountUI from "../../components/count";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction,
} from "../../redux/count_action";
const mapStateToProps = (state) => ({ count: state });
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {
dispatch(createIncrementAction(number));
},
incrementAsync: (number) => {
dispatch(createIncrementAsyncAction(number, 500));
},
decrement: (number) => {
dispatch(createDecrementAction(number));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
// UI组件
import React, { Component } from "react";
export default class CountUI extends Component {
// 加法
increment = () => {
const { value } = this.selectNumber;
this.props.increment(value * 1);
};
// 减法
decrement = () => {
const { value } = this.selectNumber;
this.props.decrement(value * 1);
};
// 奇数加
incrementIfOdd = () => {
if (this.props.count % 2 === 1) {
const { value } = this.selectNumber;
this.props.increment(value * 1);
}
};
// 异步加
incrementAsync = () => {
const { value } = this.selectNumber;
this.props.increment(value * 1);
};
render() {
return (
<div>
<h1>当前求和为: {this.props.count}</h1>
...
</div>
);
}
}
2.3 mapDispatchToProps
mapDispatchToProps
是connect
函数的第二个参数,用来建立 UI 组件的参数到store.dispatch
方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action
,传给 Store
它可以是一个函数,也可以是一个对象
- 如果mapDispatchToProps是一个函数
/ 容器组件
const mapDispatchToProps = (dispatch) => ({
increment: (number) => {
dispatch(createIncrementAction(number));
},
incrementAsync: (number) => {
dispatch(createIncrementAsyncAction(number));
},
decrement: (number) => {
dispatch(createDecrementAction(number));
},
});
// mapDispatchToProps的一般写法,返回function
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
-
如果mapDispatchToProps是一个对象
键值是一个函数,
Action creator
,返回的Action
会由Redux
自动发出
// mapDispatchToProps的简写,返回object
export default connect(mapStateToProps, {
increment: createIncrementAction,
incrementAsync: createIncrementAsyncAction,
decrement: createDecrementAction,
})(CountUI);
2.4Provider
connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成 UI 组件的参数。
一种解决方法是将state
对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
// src/App.js
import React, { Component } from "react";
import Count from "./container/count";
import store from "./redux/store";
export default class App extends Component {
render() {
return <Count store={store} />;
}
}
- React-Redux 提供
Provider
组件,可以让容器组件拿到state Provider
在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了
- 它的原理是
React
组件的context
属性- 使原来整个应用成为
Provider
的子组件 接收Redux的store作为props
,通过context
对象传递给子孙组件上的connect
// index.js
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(
// <React.StrictMode>
<Provider store={store}>
<App />
</Provider>
// </React.StrictMode>
);
2.5. 中间件,combineReducers函数,redux-devtools
-
中间件
- 中间件的目的是在
dispatch
的action
和最终达到的reducer
之间,扩展一些自己的代码; - 比如
日志记录
、调用异步接口
、添加代码调试功能
等等; redux-thunk
、applyMiddleware
用于支持异步action,
- 中间件的目的是在
-
combineReducers
函数- 它也是将我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
- 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;
- 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
-
redux-devtools
- 利用这个工具,我们可以知道每次
状态是如何被修改的
,修改前后的状态变化
- 利用这个工具,我们可以知道每次
import { applyMiddleware, compose, createStore,combineReducers } from 'redux';
import thunk from 'redux-thunk';
import count from "./conut";
import persons from "./persons";
const reducer= combineReducers({
count,
persons,
});
//创建store 传递reducer
// redux-devtools
// trace 开启
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
3. Redux Toolkit
3. 1 基本特征与API
- Redux Toolkit 也被称为
RTK
npm install @reduxjs/toolkit react-redux
- Redux Toolkit的核心API主要是如下几个:
configureStore
:包装createStore
以提供简化的配置选项和良好的默认值。它可以自动组合你的slice reducer
,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。createSlice
:接受reducer函数的对象
、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。createAsyncThunk
: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected
基于该承诺分派动作类型的 thunk
3.2 createSlice
通过createSlice
创建一个slice
,createSlice主要包含如下几个参数:
name
:用户标记slice的名词, 在之后的redux-devtool中会显示对应的名词;initialState
:初始化值,第一次初始化时的值;reducers
:相当于之前的reducer函数- 对象类型,并且可以添加很多的函数;
- 函数类似于redux原来reducer中的一个case语句;
- 函数的参数:
state和action
调用这个action时
,传递的action参数
;
extraReducers
监听异步结果
createSlice返回值是一个对象
,包含所有的actions;
import { createSlice } from '@reduxjs/toolkit';
const homeReducer = createSlice({
name: 'home',
initialState: {
banners: [],
recommends: []
},
reducers: {
changeRecommendActios(state, { paylaod }) {
state.recommends=paylaod
},
changeBannerActions(state, { payload }) {
state.banners=payload
}
}
})
export const { changeBannerActions, changeRecommendActios } = homeReducer.actions
export default homeReducer.reducer
3.3 store 创建
configureStore
用于创建store对象
,常见参数如下:reducer
,将slice中的reducer可以组成一个对象传入此处;middleware
:可以使用参数,传入其他的中间件devTools
:是否配置devTools工具,默认为true;
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/Counter';
import homeReducer from './modules/Home';
const store = configureStore({
reducer: {
// 这里做分包
counter: counterReducer,
home: homeReducer
}
})
export default store
3.4 Provide ,connect
- Provide 还是需要提供store
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from 'react-redux';
import App from "./App";
import store from './store';
const root = ReactDOM.createRoot(document.getElementById("root"));
// 1. react-redux使用 第一步提供全局store
root.render(
// 严格模式 render执行两次
<Provider store={store} >
<App />
</Provider>
);
3.5 Redux Toolkit的异步操作
Redux Toolkit默认已经给我们继承了Thunk相关的功能:createAsyncThunk
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetachHomeActions = createAsyncThunk('home/multidata', async (payload, extraInfo) => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
return res.data
})
- 当createAsyncThunk创建出来的action被dispatch时,会存在三种状态
pending
:action被发出,但是还没有最终的结果;fulfilled
:获取到最终的结果(有返回值的结果);rejected
:执行过程中有错误或者抛出了异常;
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetachHomeActions = createAsyncThunk('home/multidata', async (payload, extraInfo) => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
return res.data
})
const homeReducer = createSlice({
name: 'home',
initialState: {
banners: [],
recommends: []
},
reducers: {
changeRecommendActios(state, { paylaod }) {
state.recommends = paylaod
},
changeBannerActions(
state, { payload }
) {
state.banners = payload
}
},
// 异步操作(三种状态)
extraReducers: {
[fetachHomeActions.pending](state, action) {
console.log(action);
},
[fetachHomeActions.fulfilled](state, { payload }) {
state.banners=payload.data.banner.list
},
[fetachHomeActions.rejected](sate, action) {
}
}
})
export const { changeBannerActions, changeRecommendActios } = homeReducer.actions
export default homeReducer.reducer
链式调用
// 异步操作(三种状态)(链式调用的形式)
extraReducers: (builder) => {
builder.addCase(fetachHomeActions.pending, (state, action) => {
console.log("fetachHomeActions pending")
}).addCase(fetachHomeActions.fulfilled, (state, { payload }) => {
state.banners = payload.data.banner.list
state.recommends = payload.data.recommend.list
})
}
4. hooks,useSelector、useDispatch
- createSlice 创建reducer
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
})
- 使用
useSelector
访问/使用state
const {counter} = useSelector((state) => state.counter.counter);
// 优化
// 1. memo包裹只有只有props改变才会重新渲染
// 2.第二个参数shallowEqual 进行浅层比较 (就是reducer中返回完全相同的对象 才不进行重新渲染)
// shallowEqual 解决使用相同的参数调用时,useSelector返回不同的结果。这可能导致不必要的重新渲染。
const App = memo((props) => {
const { counter } = useSelector((state) => ({
counte: state.counter.counter
}),shallowEqual)
return (
<div>
<h1>{counter}</h1>
</div>
)
})
- 使用
useDispatch
变更state
useDispatch接收的是你在createSlice中定义的reducers里的action,比如把increment传给useDispatch即可给state加1。
import {useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
} from './counterSlice';
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span>{count}</span>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
</div>
);
}
参考文章:jjjona0215大神