Article directory
Illustration
1. redux
npm install redux --save
1.1 Overview and usage principles
- It is a dedicated tool
状态管理的js库
(not a react plugin) - Function: Centrally manage the state shared by multiple components in react applications
- use:
- The status of a certain component needs to be available to other components at any time.
(共享)
- One component needs to change the state of another component
(通信)
- The status of a certain component needs to be available to other components at any time.
1.2 Workflow
The redux workflow diagram is as follows:
1.2.1 Three cores
- action
- object of action
- Contains two attributes
type
: Attribute identifier, value is a string, unique, necessary attributedata
: Data attribute, value of any type, optional attribute- example
{type:'CHANGE_NAME', data: {name: 'why'}}
- reducer
- Used for initialization and processing status
- During processing, a new state is generated based on the old state and action.
纯函数
- example
if (type === 'CHANGE_NAME') {return { ...state, name }}
store
- Objects that connect ,
state
,action
reducer
- During processing,
state和action
a new state is generated based on the old one.纯函数
1.3 store
- The entire file is divided into modules
│ └─ store
│ ├─ actions // actions,文件夹内以模块区分
│ │ ├─ count.js
│ │ └─ person.js
│ ├─ constants.js // action type唯一标识常量
│ ├─ index.js // 入口文件
│ └─ reducer // reducer,文件夹内以模块区分
│ ├─ conut.js
│ ├─ index.js // reducer统一暴露文件,合并reducers
│ └─ persons.js
- Introduced
createStore
, specifically used to create the most core store object in redux, andredux-thunk
applyMiddleware is used to support asynchronous actions.
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
- Define
type类型
constant values in action objects
// src/store/constants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
- Create action, action return
对象
, asynchronous action return函数
can be used to send network requests, executionsetTimeout
, etc.
// 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
- The delayed action does not want to be handed over to the component itself, but to the action
- Execute asynchronous methods to operate on the status, but the specific data needs to be returned by the asynchronous task
1.5 reducer
- The reducer function will receive two parameters: the previous state
(state)
and the action object.(action)
- Obtained from the action object
type
,data
- Determine
type
how to process the data reducer
When there is no initialization value, it isundefined
so we can set the initial valueinitialState
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只读
- The only way to modify the State must be triggering
action
. Do not try to modify the State in any other way: This ensures that all modifications are processed and集中化处理
executed in strict order, so there is no need to worry about race conditions. question;
- The only way to modify the State must be triggering
-
Use
纯函数
to perform modifications- Connect the old state and actions through the reducer and return a new State:
- As the complexity of the application increases, we can
reducer拆分成多个小的reducers
operate separately; - But all reducers should be
纯函数
, and cannot produce any副作用
;
1.5.1 Merging reducers
Through combineReducers
merging, the received parameter is an object, and the key value of the object is consistent with the key value of the object obtained by getState().
// 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 and status update
- The component
getState()
gets the data from the store dispatch
trigger action- subscribe() completes view update
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 Basic characteristics
- redux needs to monitor store changes to update the view and use it
store.subscribe(() => { this.forceUpdate(); })
; react-redux does not need to monitor - react-redux divides components into
UI组件
;容器组件
redux operations are all in container components. - Single responsibility principle; by
connect(mapStateToProps, mapDispatchToProps)(UI)
connecting container components and UI components; redux has no distinction - The UI component is responsible
UI的呈现
, and the container component is responsible管理数据和逻辑
. If a component has both UI and business logic, split it into the following structure: a container component on the outside and a UI component inside. The former is responsible for communicating with the outside, passing data to the latter, and the latter renders the view.
2.2 connect()、mapStateToProps
React-Redux
Providesconnect
methods forUI组件
generating from容器组件
- In the code below,
CountUI
yesUI组件
,connect
the last exported one is容器组件
- In order to define business logic, the following two aspects of information need to be given:
Input logic: How to convert external data (ie
state对象
) into parameters of UI components
Output logic: How to convert actions issued by users into Action objects and pass them out from UI components
connect
The method accepts two parameters: mapStateToProps
and mapDispatchToProps
. They define the business logic of the UI component. The former is responsible for the input logic, which is state
mapped to the parameters of the UI component ( props
), and the latter is responsible for the output logic, that is, the user's operation on the UI component is mapped to Action
mapStateToProps
receive state
parameters, mapDispatchToProps
receive dispatch
parameters
// 容器组件
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
Is connect
the second parameter of the function, used to establish store.dispatch
the mapping of UI component parameters to methods. In other words, it defines which user operations should be treated as Action
, and Store
it can be a function or an object passed to it.
- If mapDispatchToProps is a function
/ 容器组件
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);
-
If mapDispatchToProps is an object
The key value is a function,
Action creator
, and the returned will be automatically emittedAction
byRedux
// mapDispatchToProps的简写,返回object
export default connect(mapStateToProps, {
increment: createIncrementAction,
incrementAsync: createIncrementAsyncAction,
decrement: createDecrementAction,
})(CountUI);
2.4Provider
connect
After the method generates the container component, the container component needs to get state
the object to generate the parameters of the UI component.
One solution is to pass state
the object as a parameter to the container component. However, this is more troublesome, especially since the container component may be at a very deep level, and it is very troublesome to pass the state down level by level.
// 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 provides
Provider
components that allow container components to get state Provider
A layer is wrapped around the root component, so that all sub-components of the App can get state by default.
- Its principle is that the properties
React
of the componentcontext
- Make the original entire application become
Provider
a subcomponent to receive it , and pass it to the descendant componentRedux的store作为props
through the object.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. Middleware, combineReducers function, redux-devtools
-
middleware
- The purpose of middleware is to extend some of your own code between what
dispatch
is achievedaction
and what is finally achieved ;reducer
- For example
日志记录
,调用异步接口
,添加代码调试功能
etc.; redux-thunk
,applyMiddleware
used to support asynchronous actions,
- The purpose of middleware is to extend some of your own code between what
-
combineReducers
function- It also merges the reducers we passed in into an object, and finally returns a combination function (equivalent to our previous reducer function);
- During the execution of the combination function, it will decide whether to return the previous state or the new state by judging whether the data returned before and after are the same;
- The new state will trigger the corresponding refresh of the subscriber, while the old state can effectively prevent the refresh of the subscriber;
-
redux-devtools
- Using this tool, we can know every time
状态是如何被修改的
,修改前后的状态变化
- Using this tool, we can know every time
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 Basic features and API
- Redux Toolkit is also known as
RTK
npm install @reduxjs/toolkit react-redux
- The core APIs of Redux Toolkit are mainly as follows:
configureStore
:WrappercreateStore
to provide simplified configuration options and good defaults. It automatically composes yoursslice reducer
, adding any Redux middleware you provide, redux-thunk is included by default, and enabling the Redux DevTools Extension.createSlice
: Acceptsreducer函数的对象
slice names and initial state values, and automatically generates slice reducers with corresponding actions.createAsyncThunk
: Takes an action type string and a function that returns a promise, and generates apending/fulfilled/rejected
thunk that dispatches the action type based on that promise
3.2 createSlice
By createSlice
creating one slice
, createSlice mainly contains the following parameters:
name
: The noun marked by the user as slice, the corresponding noun will be displayed in redux-devtool later;initialState
: Initialization value, the value at the first initialization;reducers
: Equivalent to the previous reducer function- Object type, and many functions can be added;
- The function is similar to a case statement in the original reducer of redux;
- Function parameters:
state和action
call thisaction时
,传递的action参数
;
extraReducers
Listen for asynchronous results
createSlice返回值是一个对象
, including all 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 creation
configureStore
For creationstore对象
, common parameters are as follows:reducer
, the reducers in the slice can be composed into an object and passed here;middleware
: You can use parameters to pass in other middlewaredevTools
: Whether to configure the devTools tool, the default is 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 still needs to provide a 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 Asynchronous operations of Redux Toolkit
Redux Toolkit has inherited Thunk-related functions for us by default: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
})
- When the action created by createAsyncThunk is dispatched, there will be three states:
pending
: The action is issued, but there is no final result yet;fulfilled
: Get the final result (result with return value);rejected
: There is an error or an exception is thrown during execution;
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
chain call
// 异步操作(三种状态)(链式调用的形式)
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 creates 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;
},
},
})
- Use
useSelector
access/use 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>
)
})
- Using
useDispatch
change state
useDispatch receives the action in the reducers you defined in createSlice. For example, passing increment to useDispatch can add 1 to the state.
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>
);
}
Reference article: jjjona0215 master