react-redux(由浅到深)


图解
在这里插入图片描述

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

  • stateactionreducer联系在一起的对象
  • 加工时,根据旧的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对象中获取typedata
  • 根据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(竟态)的问题;
  • 使用纯函数来执行修改

    • 通过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组件生成容器组件
  • 下面代码中,CountUIUI组件,利用connect最后导出的是容器组件
  • 为了定义业务逻辑,需要给出下面两方面的信息:

输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI组件传出去

connect方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 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

mapDispatchToPropsconnect函数的第二个参数,用来建立 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

  • 中间件

    • 中间件的目的是在dispatchaction和最终达到的reducer之间,扩展一些自己的代码;
    • 比如日志记录调用异步接口添加代码调试功能等等;
    • redux-thunkapplyMiddleware用于支持异步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大神

猜你喜欢

转载自blog.csdn.net/weixin_46104934/article/details/131500441