react-redux (from shallow to deep)


Illustration
Insert image description here

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(通信)

1.2 Workflow

The redux workflow diagram is as follows:
Insert image description here

1.2.1 Three cores

  • action
    • object of action
    • Contains two attributes
      • type: Attribute identifier, value is a string, unique, necessary attribute
      • data: 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,actionreducer
  • During processing, state和actiona 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, and redux-thunkapplyMiddleware 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, execution setTimeout, 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 typehow to process the data
  • reducerWhen there is no initialization value, it is undefinedso 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;
  • 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拆分成多个小的reducersoperate separately;
    • But all reducers should be 纯函数, and cannot produce any 副作用;

1.5.1 Merging reducers

Through combineReducersmerging, 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
  • dispatchtrigger 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.
    Insert image description here

2.2 connect()、mapStateToProps

  • React-ReduxProvides connectmethods for UI组件generating from容器组件
  • In the code below, CountUIyes UI组件, connectthe 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

connectThe method accepts two parameters: mapStateToPropsand mapDispatchToProps. They define the business logic of the UI component. The former is responsible for the input logic, which is statemapped 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
mapStateToPropsreceive stateparameters, mapDispatchToPropsreceive dispatchparameters

// 容器组件
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

mapDispatchToPropsIs connectthe second parameter of the function, used to establish store.dispatchthe 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 emitted ActionbyRedux

// mapDispatchToProps的简写,返回object
export default connect(mapStateToProps, {
    
    
  increment: createIncrementAction,
  incrementAsync: createIncrementAsyncAction,
  decrement: createDecrementAction,
})(CountUI);

2.4Provider

connectAfter the method generates the container component, the container component needs to get statethe object to generate the parameters of the UI component.
One solution is to pass statethe 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 Providercomponents that allow container components to get state
  • ProviderA 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 Reactof the componentcontext
  • Make the original entire application become Providera subcomponent to receive it , and pass it to the descendant component Redux的store作为propsthrough the object.contextconnect
// 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 dispatchis achieved actionand what is finally achieved ;reducer
    • For example 日志记录, 调用异步接口, 添加代码调试功能etc.;
    • redux-thunk, applyMiddlewareused to support asynchronous actions,
  • combineReducersfunction

    • 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 状态是如何被修改的,修改前后的状态变化
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 asRTK

npm install @reduxjs/toolkit react-redux

  • The core APIs of Redux Toolkit are mainly as follows:
    • configureStore:Wrapper createStoreto provide simplified configuration options and good defaults. It automatically composes yours slice reducer, adding any Redux middleware you provide, redux-thunk is included by default, and enabling the Redux DevTools Extension.
    • createSlice: Accepts reducer函数的对象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 a pending/fulfilled/rejectedthunk that dispatches the action type based on that promise

3.2 createSlice

By createSlicecreating 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和actioncall this action时, 传递的action参数;
  • extraReducersListen 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

  • configureStoreFor creation store对象, 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 middleware
    • devTools: 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 useSelectoraccess/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 useDispatchchange 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

Guess you like

Origin blog.csdn.net/weixin_46104934/article/details/131500441