React 之 Redux - 状态管理

news2024/10/5 13:39:59

一、前言

1. 纯函数

函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念
  • 确定的输入,一定会产生确定的输出

  • 函数在执行过程中,不能产生副作用

2. 副作用

表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,
比如修改了全局变量,修改参数或者改变外部的存储
  • 纯函数在执行的过程中就是不能产生这样的副作用

  • 副作用往往是产生bug的 “温床”

3. 纯函数的案例

对数组操作的两个函数:
slice就是一个纯函数,不会修改数组本身,而splice函数不是一个纯函数
  • slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组

  • splice:splice截取数组, 会返回一个新的数组, 也会对原数组进行修改

4. 判断是否纯函数

5. 纯函数的作用和优势

纯函数在函数式编程中非常重要 :
  • 可以安心的编写和安心的使用

  • 在写的时候保证了函数的纯度,只是单纯实现业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改

  • 在用的时候,确定的输入内容不会被任意篡改,并且确定的输入,一定会有确定的输出

6. 在React中

React中就要求无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改

7. 为啥使用Redux

二、Redux的核心理念

1. 核心理念 - store

store : 用于存储共享数据的仓库

2. 核心理念 - action

action : store中所有数据的变化,必须通过派发(dispatch)action来更新
action是一个普通的JavaScript对象,用来描述这次更新的type和content

3. 核心理念 - reducer

reducer : reducer是一个纯函数,将state和action联系在一起
reducer做的事情就是将传入的state和action结合起来生成一个新的state

4. Redux的三大原则

单一数据源

  • 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store

  • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护

  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改

State是只读的

  • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State

  • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state

  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题

使用纯函数来执行修改

  • 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State

  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分

  • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

三、Redux的基本使用

1. 测试项目搭建

01. 创建一个新的项目文件夹: learn-redux

02. 进入文件夹后进行项目初始化
npm init -y

03. 安装redux
npm install redux

04. 创建src目录,以及其中的index.js文件

05. 修改package.json可以执行index.js
也可不修改,直接控制台输入 node src/index.js

"scripts": {
  // node v13.2.0之后,添加属性:"type": "module",就可以在node中对ES6模块化的支持
  "type": "module",
  "start": "node src/index.js"
},

06. 在src中创建目录store,并创建index.js

// 需要配置下type,否则的话直接使用commonjs也是可以的
import { createStore } from 'redux';

// 1. 定义初始化数据
const initialState = {
  name: 'coder',
  age: 18,
  counter: 100
};

// 2. 定义reducer纯函数
// 2.1 reducer函数的第一个参数是state,第二个参数是action
// 2.2 reducer函数必须返回一个新的state,这个state会覆盖原来的state
// 2.3 reducer函数中,不能修改原来的state,必须返回一个新的state

// 第一次调用reducer函数的时候,state是undefined,所以给state设置默认值,只会在第一次调用的时候生效
const reducer = (state = initialState, action) => {
  
  // 2.4 在这里,根据action的type,来决定如何修改state,返回一个新的state
  switch (action.type) {
    case 'xxx' : return {...state,...}
  }
  // 2.5 没有数据更新的时候,返回一个新的state
  return state;
};

// 3. 创建store
export const store = createStore(reducer);

2. 使用store中的数据


// 1. 导入store
import { store } from './store/index.js';

// 2. 拿到store中存储的数据
console.log(store.getState()); // { name: 'coder', age: 18, counter: 100 }

3. 修改store中的数据

执行代码

// 导入store
import { store } from './store/index.js';

// 创建一个action对象
const nameAction = { type: 'change_name', name: 'star' };
// 进行派发, 派发的时候,会调用reducer函数,传入state和action
store.dispatch(nameAction)

// 创建一个action对象
const counterAction = { type: 'increment_counter', number: 10 };
// 进行派发
store.dispatch(counterAction);

console.log(store.getState()); // { name: 'star', age: 18, counter: 110 }
在reducer函数中新增判断

const reducer = (state = initialState, action) => {
  // action => { type: 'change_name', name: 'star' }
  switch (action.type) {
    case 'change_name':
      return {
        // 先对原来staet进行解构
        ...state,
        // 再对name进行覆盖
        name: action.name
      };
    case 'increment_counter':
      return { ...state, counter: state.counter + action.number };
    default:
      // 如果没有匹配到action.type,就返回原来的state
      return state;
  }
};

4. 订阅store中的数


// 导入store
import { store } from './store/index.js';

// 监听state的变化, 只要state发生了变化,就会调用回调函数
// 返回值是一个函数,调用这个函数,就可以取消监听
const unSubscribe = store.subscribe(() => {
  console.log('state发生了变化');
  console.log(store.getState());
});

// 派发action => 可以监听到
store.dispatch({ type: 'change_name', name: 'star' });

// 取消监听
unSubscribe();

// 派发action => 不会监听到,但是state会发生变化,因为store.dispatch会调用reducer函数
store.dispatch({ type: 'increment_counter', number: 10 });

5. 优化

动态生成action


// 导入store
import { store } from './store/index.js';

store.subscribe(() => {
  console.log('state发生了变化', store.getState());
});

// 动态生成action : actionCreator => 创建action的函数, 返回一个action对象
const changeNameAction = (name) => ({ type: 'change_name', name });

// store.dispatch({ type: 'change_name', name: 'star' });
// store.dispatch({ type: 'change_name', name: 'coderstar' });
store.dispatch(changeNameAction('star'));
store.dispatch(changeNameAction('coderstar'));

目录结构优化

如果将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护
对代码进行拆分,将store、reducer、action、constants拆分成一个个文件

创建store/index.js文件

import { createStore } from 'redux';

import reducer from './reducer.js';

// 创建store
export const store = createStore(reducer);
创建store/reducer.js文件

import { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';

// 1. 定义初始化数据
const initialState = {
  name: 'coder',
  age: 18,
  counter: 100
};

// 2. 定义reducer
export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case CHANGE_NAME:
      return {
        // 先对原来staet进行解构
        ...state,
        // 再对name进行覆盖
        name: action.name
      };
    case INCREMENT_COUNTER:
      return { ...state, counter: state.counter + action.number };
    default:
      // 如果没有匹配到action.type,就返回原来的state
      return state;
  }
};
创建store/actionCreators.js文件 => 用于生成action

// Description: Action Creators

import { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';

// 修改name名字的action
export const changeNameAction = (name) => ({ type: CHANGE_NAME, name });

// 修改counter的action
export const incrementCounterAction = (number) => ({ type: INCREMENT_COUNTER, number });
创建store/constants.js文件 => 用于定义type常量

// Desc: constants for actions

export const CHANGE_NAME = 'change_name';
export const INCREMENT_COUNTER = 'increment_counter';

6. Redux使用流程图

四、React结合Redux

1. 安装

npm install redux

2. 基本使用

 store文件夹

index.js

import { createStore } from 'redux';
import { reducer } from './reducer';

export const store = createStore(reducer);

 constants.js

export const CHANGE_COUNTER = 'change_counter';

export const CHANGE_BANNER = 'change_banner';

reducer.js

import { CHANGE_BANNER, CHANGE_COUNTER } from './constants';

const initialState = {
  counter: 20,
  bannerList: []
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case CHANGE_COUNTER:
      return { ...state, counter: state.counter + action.counter };
    case CHANGE_BANNER:
      return { ...state, bannerList: [...state.bannerList, ...action.bannerList] };
    default:
      return state;
  }
};

actionCreatores.js

import { CHANGE_COUNTER, CHANGE_BANNER } from './constants';

// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });

// 修改bannerList的action
export const changeBannerAction = (bannerList) => ({ type: CHANGE_BANNER, bannerList });

page文件夹

Home.jsx

import React, { PureComponent } from 'react';
import { store } from '../store';
import { changeCounterAction } from '../store/actionCreatores.js';

export class Home extends PureComponent {
  constructor(porps) {
    super(porps);

    this.state = {
      // 从store中获取counter的值,赋予初始化值
      counter: store.getState().counter
    };
  }
  componentDidMount() {
    // 监听store的变化
    store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      });
    });
  }
  changeCounter(num) {
    // 改变store中的值,通过dispatch派发action
    store.dispatch(changeCounterAction(num));
  }
  render() {
    const { counter } = this.state;
    return (
      <>
        <h2>Home counter : {counter}</h2>
        <button onClick={(e) => this.changeCounter(5)}>+5</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(10)}>+10</button>
      </>
    );
  }
}

export default Home;

Profily.jsx

import React, { PureComponent } from 'react';
import { store } from '../store';
import { changeCounterAction } from '../store/actionCreatores.js';

export class Profily extends PureComponent {
  constructor(porps) {
    super(porps);

    this.state = {
      // 从store中获取counter的值,赋予初始化值
      counter: store.getState().counter
    };
  }
  componentDidMount() {
    // 监听store的变化
    store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      });
    });
  }
  changeCounter(num) {
    // 改变store中的值,通过dispatch派发action
    store.dispatch(changeCounterAction(num));
  }
  render() {
    const { counter } = this.state;
    return (
      <>
        <h2>Profily counter : {counter}</h2>
        <button onClick={(e) => this.changeCounter(-5)}>-5</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(-10)}>-10</button>
      </>
    );
  }
}

export default Profily;

App.jsx

import React, { PureComponent } from 'react';
import Home from './page/Home';
import Profily from './page/Profily';

import { store } from './store';

export class App extends PureComponent {
  constructor(porps) {
    super(porps);

    this.state = {
      // 从store中获取counter的值,赋予初始化值
      counter: store.getState().counter
    };
  }
  componentDidMount() {
    // 监听store的变化
    store.subscribe(() => {
      this.setState({
        counter: store.getState().counter
      });
    });
  }
  render() {
    const { counter } = this.state;
    return (
      <div style={{ textAlign: 'center', marginTop: '100px' }}>
        <h2>App counter : {counter}</h2>
        <hr />
        <hr />
        <hr />
        <Home />
        <hr />
        <hr />
        <hr />
        <Profily />
      </div>
    );
  }
}

export default App;

3. react-redux的使用

  • redux和react没有直接的关系,完全可以在React, Angular, Ember, jQuery, or vanilla 
    JavaScript中使用Redux

  • redux依然是和React库结合的更好

  • redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效

将组件和store连接

安装 => npm install react-redux

1. 修改index.js 

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
// 引入Provider组件, 用于给整个项目提供一个公共的store
import { Provider } from 'react-redux';
import { store } from './store';

const root = ReactDOM.createRoot(document.querySelector('#root'));

root.render(
  <React.StrictMode>
    {/* 给整个项目提供一个公共的store */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

2. 组件中使用

需要进行映射,state、dispatch都需要映射

import React, { PureComponent } from 'react';
// 1. 引入connect,用于连接组件和redux,返回一个高阶组件
import { connect } from 'react-redux';
import { changeCounterAction } from '../store/actionCreatores';

export class About extends PureComponent {
  changeCounter(num) {
    // 4. 从props中取出changeCounter,不再从state中取,因为state中的值已经被映射到props中了
    const { changeCounter } = this.props;
    // 执行了下面定义的方法,相当于调用了dispatch(changeCounterAction(num))
    changeCounter(num);
  }
  render() {
    // 3. 从props中取出counter,不再从state中取,因为state中的值已经被映射到props中了
    const { counter } = this.props;
    return (
      <>
        <h2>About : {counter}</h2>
        <button onClick={(e) => this.changeCounter(8)}>+8</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(-8)}>-8</button>
      </>
    );
  }
}

/**
 * connect => connect()()返回一个高阶组件,第一个()传入两个参数,第二个()传入一个组件,返回一个新的 高阶组件🌟
 * 第一个()传入两个参数,第一个参数是一个函数,第二个参数是一个对象
 *    第一个参数函数,函数的参数是state,返回一个对象,对象的属性是state,值是state
 *    第二个参数对象,对象的属性是一个函数,函数的参数是dispatch,返回一个对象,对象的属性是函数,函数的参数是参数,返回一个action
 * 第二个()传入一个组件,返回一个新的组件
 */
// 2. 注入store中的state和dispatch到组件的props中

// 用来映射state中的值到组件的props中,可以设置取其中需要的数据
const mapStateToProps = (state) => ({ counter: state.counter });
// 用来映射dispatch到组件的props中,可以设置取其中需要的方法
const mapDispatchToProps = (dispatch) => ({
  changeCounter: (num) => dispatch(changeCounterAction(num))
});

export default connect(mapStateToProps, mapDispatchToProps)(About);

3. 组件中异步操作

About组件        =>   请求banners数据
Category组件   =>   展示banners数据

效果

About组件 

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction, changeBannerAction } from '../store/actionCreatores';
import axios from 'axios';

export class About extends PureComponent {
  changeCounter(num) {
    const { changeCounter } = this.props;
    changeCounter(num);
  }
  async getData() {
    const res = await axios.get('http://xxxxxx');
    const bannerList = res?.data?.data?.banner?.list || [];
    const { changeBanner } = this.props;
    // 请求数据后,改变store中的值
    changeBanner(bannerList);
  }
  render() {
    const { counter } = this.props;
    return (
      <>
        <h2>About : {counter}</h2>
        <button onClick={(e) => this.changeCounter(8)}>+8</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(-8)}>-8</button>
        {'                                                       '}
        <button onClick={(e) => this.getData()}>请求数据</button>
      </>
    );
  }
}

const mapStateToProps = (state) => ({ counter: state.counter });
const mapDispatchToProps = (dispatch) => ({
  changeCounter: (num) => dispatch(changeCounterAction(num)),
  changeBanner: (bannerList) => dispatch(changeBannerAction(bannerList))
});

export default connect(mapStateToProps, mapDispatchToProps)(About);

Category组件

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';

export class Category extends PureComponent {
  render() {
    const { bannerList } = this.props;
    return (
      <>
        <h2>Category</h2>
        <ul>
          {bannerList.map((item, index) => {
            return <li key={index}>{item.title}</li>;
          })}
        </ul>
      </>
    );
  }
}

const mapStateToProps = (state) => ({ bannerList: state.bannerList });

export default connect(mapStateToProps, null)(Category);

4. redux中异步操作

上面的代码有一个缺陷

  • 必须将网络请求的异步代码放到组件的生命周期中来完成
  • 事实上,网络请求到的数据也属于状态管理的一部分,更好的一种方式应该是将其也交给redux来管理

redux中如何可以进行异步的操作 : 

  • 答案就是使用中间件(Middleware)
  • 学习过Express或Koa框架的童鞋对中间件的概念一定不陌生
  • 在这类框架中,Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie解析、日志记录、文件压缩等操作

中间件

redux也引入了中间件(Middleware)的概念:

  • 这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码
  • 比如日志记录、调用异步接口、添加代码调试功能等等
  • 发送异步的网络请求,可以使用中间件是 redux-thunk

redux-thunk是如何做到让我们可以发送异步的请求呢

  • 我们知道,默认情况下的dispatch(action),action需要是一个JavaScript的对象
  • redux-thunk可以让dispatch(action函数),action可以是一个函数
  • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数
    • dispatch函数用于我们之后再次派发action
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

redux-thunk的使用

安装    =>     npm install redux-thunk

store/index.js

// applyMiddleware => 应用中间件
import { createStore, applyMiddleware } from 'redux';

import thunk from 'redux-thunk';
import { reducer } from './reducer';

// 正常情况下,store.dispatch只能接收对象 store.dispatch(对象object)

// 想要派发函数store.dispatch(函数function),需要对store进行增强,增强的方式是使用中间件
// 但是如果使用了redux-thunk中间件,那么store.dispatch就可以接收函数 store.dispatch(函数function)

// 1. 引入中间件 applyMiddleware(thunk, logger, ...) => 可以传入多个中间件
const enhancer = applyMiddleware(thunk);
// 2. 使用中间件
export const store = createStore(reducer, enhancer);

actionCreatores.js

import axios from 'axios';
import { CHANGE_COUNTER, CHANGE_BANNER } from './constants';

// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });

// 修改bannerList的action
export const changeBannerAction = (bannerList) => ({ type: CHANGE_BANNER, bannerList });

// 异步获取bannerList的action
export const getBannerListAction = () => {
  // 如果是普通的action,那么返回的是一个对象, {type: 'xxx', payload: 'xxx'}
  // 问题:对象中不能直接拿到从服务器获取的数据
  // return {}

  // 如果是函数,那么redux是不支持的,需要使用中间件redux-thunk
  // 一旦派发来到这个函数,那么redux-thunk就会自动执行这个函数
  // 这个函数的参数有来两个,第一个是dispatch => store.dispatch dispatch(xxx) , 第二个是getState => 获取store中的数据 getState().counter
  return async (dispatch, getState) => {
    const res = await axios.get('http://123.207.32.32:8000/home/multidata');
    const bannerList = res?.data?.data?.banner?.list || [];
    // 派发action对象
    dispatch(changeBannerAction(bannerList));
  };
};

About.jsx

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction, getBannerListAction } from '../store/actionCreatores';

export class About extends PureComponent {
  changeCounter(num) {
    const { changeCounter } = this.props;
    changeCounter(num);
  }
  getData() {
    const { getBannerList } = this.props;
    // 在这里调用store中的action发送异步请求,获取数据
    getBannerList();
  }
  render() {
    const { counter } = this.props;
    return (
      <>
        <h2>About : {counter}</h2>
        <button onClick={(e) => this.changeCounter(8)}>+8</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(-8)}>-8</button>
        {'                                                       '}
        <button onClick={(e) => this.getData()}>请求数据</button>
      </>
    );
  }
}

const mapStateToProps = (state) => ({ counter: state.counter });
const mapDispatchToProps = (dispatch) => ({
  changeCounter: (num) => dispatch(changeCounterAction(num)),
  getBannerList: () => dispatch(getBannerListAction())
});

export default connect(mapStateToProps, mapDispatchToProps)(About);

5. react调试工具 - redux-devtools

redux可以方便的让我们对状态进行跟踪和调试

  • redux官网为我们提供了redux-devtools的工具
  • 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等

步骤一 

在对应的浏览器中安装相关的插件

比如Chrome浏览器扩展商店中搜索Redux DevTools即可

步骤二

在redux中继承devtools的中间件

import { createStore, applyMiddleware, compose } from 'redux';

import thunk from 'redux-thunk';
import { reducer } from './reducer';

// redux-devtools-extension,用于在浏览器中查看redux的状态 => 为了安全,只在开发环境中使用
// 生产环境
let composeEnhancers = compose;
if (process.env.NODE_ENV === 'development') {
  // 开发环境
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
}
const enhancer = applyMiddleware(thunk);

export const store = createStore(reducer, composeEnhancers(enhancer));

6. Reducer文件拆分

拆分 

actionCreatores.js

import { CHANGE_COUNTER } from './constants';

// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });

constants.js

export const CHANGE_COUNTER = 'change_counter';

index.js

// 文件做统一的导出

export { reducer as counterReducer } from './reducer';

export * from './actionCreatores';

reducer.js

import { CHANGE_COUNTER } from './constants';

const initialState = {
  counter: 20
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case CHANGE_COUNTER:
      return { ...state, counter: state.counter + action.counter };
    default:
      return state;
  }
};

store   =>   下的index.js文件,用来合并各个模块的reducer

combineReducers函数可以方便的让我们对多个reducer进行合并

import { createStore, applyMiddleware, compose, combineReducers } from 'redux';

import thunk from 'redux-thunk';

import { counterReducer } from './counter';
import { homeReducer } from './home';

// 合并reducer
const reducer = combineReducers({
  // 相当于模块名称
  counter: counterReducer,
  home: homeReducer
});

// redux-devtools-extension,用于在浏览器中查看redux的状态 => 为了安全,只在开发环境中使用
// 生产环境
let composeEnhancers = compose;
if (process.env.NODE_ENV === 'development') {
  // 开发环境
  composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
}
const enhancer = applyMiddleware(thunk);

export const store = createStore(reducer, composeEnhancers(enhancer));

使用

直接取数据或者映射数据时,需要加上模块名

派发action还是和以前一样,不需要更改

五、ReduxToolkit

1. 概念

Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法

  • 在前面我们学习Redux的时候应该已经发现,redux的编写逻辑过于的繁琐和麻烦
  • 并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理)
  • Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题
  • 在很多地方为了称呼方便,也将之称为“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

2. 基本使用

store中的代码

counter.js   =>     store/peature/counter.js

// 1. 导入创建切片的函数
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  // 2. 切片名称
  name: 'counter',
  // 3. 初始状态
  initialState: {
    counter: 88
  },
  // 4. reducers => 相当于之前的reducer
  reducers: {
    // 5. 相当于之前的case,action.type就是这里的方法名称
    changeCounterAction(state, action) {
      state.counter += action.payload;
    }
  }
});

// 6. 导出actions
export const { changeCounterAction } = counterSlice.actions;

// 7. 导出reducer
export default counterSlice.reducer;

index.js   =>     store/index.js

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter';
import categoryReducer from './features/category';

import thunk from 'redux-thunk';

const store = configureStore({
  // 1. reducer : 将切片的reducer添加到store中
  reducer: {
    counter: counterReducer,
    category: categoryReducer
  },
  // 2. devTools : 开启redux-devtools,默认开启,开发环境开启,生产环境关闭
  devTools: process.env.NODE_ENV === 'development',
  // 3. middleware : 中间件,默认只有thunk,如果需要添加其他中间件,可以在这里添加
  middleware: (getDefaultMiddleware) => { 
    return getDefaultMiddleware().concat(thunk);
  },

  // 不常用的配置项
  // 4. enhancers : 增强器,如果需要添加其他增强器,可以在这里添加
  enhancers: [],
  // 5. preloadedState : 初始状态,如果需要添加初始状态,可以在这里添加
  preloadedState: {},
  // 6. reducerPathInfo : reducer路径信息,如果需要添加reducer路径信息,可以在这里添加
  reducerPathInfo: {},
  // 7. middlewareFactories : 中间件工厂,如果需要添加中间件工厂,可以在这里添加
  middlewareFactories: {},
  // 8. devToolsEnhancerOptions : devTools增强器选项,如果需要添加devTools增强器选项,可以在这里添加
  devToolsEnhancerOptions: {},
  // 9. immutableCheck : 是否开启不可变检查,默认开启
  immutableCheck: true,
  // 10. serializableCheck : 是否开启序列化检查,默认开启
  serializableCheck: true,
  // 11. middlewareOptions : 中间件选项,如果需要添加中间件选项,可以在这里添加
  middlewareOptions: {},
  // 12. thunk : thunk中间件选项,如果需要添加thunk中间件选项,可以在这里添加
  thunk: {},
});

export default store;

主体index.js中的代码

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.querySelector('#root'));

root.render(
  <React.StrictMode>
    {/* 给整个项目提供一个公共的store */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

page中的代码

About.jsx    =>     page/About.jsx

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction } from '../store/features/counter';

export class About extends PureComponent {
  changeCounter(num) {
    // 通过props获取action
    const { changeCounterAction } = this.props;
    // 调用action
    changeCounterAction(num);
  }
  render() {
    const { counter } = this.props;
    return (
      <>
        <h2>About : {counter}</h2>
        <button onClick={(e) => this.changeCounter(8)}>+8</button>
        {'                                                       '}
        <button onClick={(e) => this.changeCounter(-8)}>-8</button>
      </>
    );
  }
}

// 映射Redux全局的state到组件的props上 => counter
const mapStateToProps = (state) => ({ counter: state.counter.counter });
// 映射dispatch到props上
const mapDispatchToProps = (dispatch) => ({
  changeCounterAction: (num) => dispatch(changeCounterAction(num)),
});

export default connect(mapStateToProps, mapDispatchToProps)(About);

3. 异步操作

之前通过redux-thunk中间件让dispatch中可以进行异步操作。
Redux Toolkit默认已经继承了Thunk相关的功能:createAsyncThunk

teAsyncThunk创建出来的action被dispatch时,会存在三种状态:

  • pending:action被发出,但是还没有最终的结果
  • fulfilled:获取到最终的结果(有返回值的结果)
  • rejected:执行过程中有错误或者抛出了异常
  • 可以在createSlice的entraReducer中监听这些结果

category.js   =>     store/peature/category.js

import axios from 'axios';
import { createSlice } from '@reduxjs/toolkit';
// 1. 导入createAsyncThunk,用于创建异步action
import { createAsyncThunk } from '@reduxjs/toolkit';

// 2. 创建异步action
export const getBannerListAction = createAsyncThunk(
  'home/getBannerListAction',
  async (extraInfo, store) => {
    // extraInfo  =>   是调用异步action时传入的参数 => {name: 'coder'}
    // store      =>   store对象,可以获取到store中的数据和dispatch方法,但是不推荐使用

    const res = await axios.get('http://123.207.32.32:8000/home/multidata');

    // 不能直接返回res,不支持序列化会报错,需要返回res.data
    return res.data;
  }
);

const categorySlick = createSlice({
  name: 'category',
  initialState: {
    bannerList: []
  },
  reducers: {
    changeBannerListAction(state, { payload }) {
      state.bannerList = payload;
    }
  },
  // 3. 添加异步action的处理逻辑
  extraReducers: {
    // 不需要监听的话,可以不写
    [getBannerListAction.pending]: (state, { payload }) => {
      console.log('pending');
    },
    // 4. 在这里拿到异步action的结果,然后修改state
    [getBannerListAction.fulfilled]: (state, { payload }) => {
      state.bannerList = payload.data.banner.list;
    },
    // 不需要监听的话,可以不写
    [getBannerListAction.rejected]: (state, { payload }) => {
      console.log('rejected');
    }
  }
});

// 6. 导出actions
export const { changeBannerListAction } = categorySlick.actions;

// 7. 导出reducer
export default categorySlick.reducer;

Category.jsx   =>     page/Category.jsx 

import React, { PureComponent } from 'react';
import { connect } from 'react-redux';

import { getBannerListAction } from '../store/features/category';
export class Category extends PureComponent {
  componentDidMount() {
    // 2. 在redux中进行数据请求
    this.props.getBannerListAction();
  }
  render() {
    // 4. 获取redux中的数据
    const { bannerList } = this.props;
    return (
      <>
        <h2>Category</h2>
        <ul>
          {bannerList.map((item, index) => {
            return <li key={index}>{item.title}</li>;
          })}
        </ul>
      </>
    );
  }
}

// 3. 映射redux中的数据
const mapStateToProps = (state) => ({ bannerList: state.category.bannerList });

// 1. 映射redux中的方法
const mapDispatchToProps = (dispatch) => ({
  // 这里可以传递参数过去
  getBannerListAction: () => dispatch(getBannerListAction({ name: 'coder' }))
});
export default connect(mapStateToProps, mapDispatchToProps)(Category);

4. 异步操作的另一种写法

extraReducer还可以传入一个函数,函数接受一个builder参数

/**
 *  extraReducer还可以传入一个函数,函数接受一个builder参数
 *    builder对象中包含了pending、fulfilled、rejected三个属性,分别对应异步action的三种状态
 *    builder对象中的属性值都是函数,函数的第一个参数是state,第二个参数是action
 *    builder对象中的属性值函数的返回值会直接赋值给state
 */
extraReducers: (builder) => {
  builder
    .addCase(getBannerListAction.pending, (state, { payload }) => {
      console.log('pending');
    })
    .addCase(getBannerListAction.fulfilled, (state, { payload }) => {
      state.bannerList = payload.data.banner.list;
    })
    .addCase(getBannerListAction.rejected, (state, { payload }) => {
      console.log('rejected');
    });
}

5. Redux Toolkit的数据不可变性

六、其他补充

1. 自定义实现connect

组件中引入该文件即可,用法一致

实现代码

connect.js   =>     hoc/Category.js

/**
 * 自定义实现 connect 函数
 * @param {*} mapStateToProps => 函数
 * @param {*} mapDispatchToProps => 函数
 * @returns => 返回值也是函数,是个高级组件,接收一个组件作为参数
 */

// 1. 导入stroe
import store from '../store';

export default function connect(mapStateToProps, mapDispatchToProps) {
  // 返回一个高级组件
  return function (WrappedComponent) {
    class NewComponent extends WrappedComponent {
      constructor(props) {
        super(props);
        // 2. 给state赋初始值
        this.state = mapStateToProps(store.getState());
      }
      componentDidMount() {
        // 3. 订阅store的变化, 一旦store发生变化,就会执行回调函数
        this.unSubscribe = store.subscribe(() => {
          // 4. 更新state
          this.setState(mapStateToProps(store.getState()));

          // 直接调用父类的forceUpdate方法,强制更新, 但是不推荐使用
          // this.forceUpdate();
        });
      }
      componentWillUnmount() {
        // 5. 取消订阅
        this.unSubscribe();
      }

      render() {
        // 6. 拿到stateObj
        const stateObj = mapStateToProps(store.getState());
        // 7. 拿到dispatchObj
        const dispatchObj = mapDispatchToProps(store.dispatch);
        // 8. 合并stateObj和dispatchObj,传递给WrappedComponent

        return <WrappedComponent {...this.props} {...stateObj} {...dispatchObj} />;
      }
    }
    return NewComponent;
  };
}

优化代码

上面的connect函数有一个很大的缺陷:依赖导入的store

正确的做法是我们提供一个Provider,Provider来自于我们创建的Context,让用户将store传入到value中即可

  主体index.js中的代码

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux';
import store from './store';
// 导入StoreContext
import { StoreContext } from './hoc';

const root = ReactDOM.createRoot(document.querySelector('#root'));

root.render(
  <React.StrictMode>
    <Provider store={store}>
      {/* 3. 使用StoreContext.Provider包裹App组件 => 把store传进去 */}
      <StoreContext.Provider value={store}>
        <App />
      </StoreContext.Provider>
    </Provider>
  </React.StrictMode>
);

connect.js   =>     hoc/connect.js

/**
 * 自定义实现 connect 函数
 * @param {*} mapStateToProps => 函数
 * @param {*} mapDispatchToProps => 函数
 * @returns => 返回值也是函数,是个高级组件,接收一个组件作为参数
 */

// 不直接引入store, 而是从storeContext中获取
// import store from '../store';

// 1. 引入storeContext
import { StoreContext } from './index.js';

// 3. 这里的context就是store   =>   因为在index.js中,使用storeContext.Provider包裹了App组件,把store传进去了

export function connect(mapStateToProps, mapDispatchToProps) {
  // 返回一个高级组件
  return function (WrappedComponent) {
    class NewComponent extends WrappedComponent {
      // 这里有两个参数,props和context,context就是store
      constructor(props, context) {
        super(props);
        this.state = mapStateToProps(context.getState());
      }
      componentDidMount() {
        this.unSubscribe = this.context.subscribe(() => {
          this.setState(mapStateToProps(this.context.getState()));
        });
      }
      componentWillUnmount() {
        this.unSubscribe();
      }

      render() {
        const stateObj = mapStateToProps(this.context.getState());
        const dispatchObj = mapDispatchToProps(this.context.dispatch);

        return <WrappedComponent {...this.props} {...stateObj} {...dispatchObj} />;
      }
    }

    // 2. 使用storeContext.Consumer包裹NewComponent组件, 传入store
    NewComponent.contextType = StoreContext;
    return NewComponent;
  };
}

storeContext.js   =>     hoc/storeContext.js

// 1. 引入createContext, 用于创建上下文
import { createContext } from 'react';

// 2. 创建上下文
export const StoreContext = createContext();

index.js   =>     hoc/index.js

// 统一的导出

export { connect } from './connect';

export { StoreContext } from './storeContext';

2. 打印日志需求

在dispatch之前 : 打印一下本次的action对象

在dispatch完成之后 : 打印一下最新的store state

01 - 手动修改

缺陷非常明显:

  • 首先,每一次的dispatch操作,都需要在前面加上这样的逻辑代码
  • 其次,存在大量重复的代码,会非常麻烦和臃肿

02 - 修改dispatch

利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑

  • 直接修改了dispatch的调用过程
  • 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog
// 导出之前,做一层拦截,派发action之前,做一些事情,比如  =>  打印日志
function interceptStoreLog(store) {
  // 1. 拿到原始的dispatch方法
  const next = store.dispatch;
  // 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法
  store.dispatch = function dispatchAndLog(action) {
    console.log('当前派发的action', action);
    // 3. 调用原始的dispatch方法,派发action
    next(action);
    console.log('派发之后的结果', store.getState());
  };
}
interceptStoreLog(store);

export default store;

3. 自定义实现thunk中间件

// 自定义实现thunk中间件
function thunkMiddleware(store) {
  // 1. 拿到原始的dispatch方法
  const next = store.dispatch;
  // 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法
  store.dispatch = function dispatchAndThunk(action) {
    // 3. 判断action是不是函数
    if (typeof action === 'function') {
      // 4. 如果是函数,就调用这个函数,并且传入dispatch和getState方法 => 使用新的dispatch方法,因为可能又派发了函数
      action(store.dispatch, store.getState);
    } else {
      // 5. 如果是对象,就调用原始的dispatch方法
      next(action);
    }
  };
}
thunkMiddleware(store);

4. 合并自定义的中间件

目录修改

log.js        =>        middleware/log.js

// 导出之前,做一层拦截,派发action之前,做一些事情,比如  =>  打印日志
export default function logMiddleware(store) {
  // 1. 拿到原始的dispatch方法
  const next = store.dispatch;
  // 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法
  store.dispatch = function dispatchAndLog(action) {
    console.log('当前派发的action', action);
    // 3. 调用原始的dispatch方法,派发action
    next(action);
    console.log('派发之后的结果', store.getState());
  };
}

thunk.js        =>        middleware/thunk.js

// 自定义实现thunk中间件
export default function thunkMiddleware(store) {
  // 1. 拿到原始的dispatch方法
  const next = store.dispatch;
  // 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法
  store.dispatch = function dispatchAndThunk(action) {
    // 3. 判断action是不是函数
    if (typeof action === 'function') {
      // 4. 如果是函数,就调用这个函数,并且传入dispatch和getState方法 => 使用新的dispatch方法,因为可能又派发了函数
      action(store.dispatch, store.getState);
    } else {
      // 5. 如果是对象,就调用原始的dispatch方法
      next(action);
    }
  };
}

index.js        =>        middleware/thunk.js 

export { default as logMiddleware } from './log';
export { default as thunkMiddleware } from './thunk';

合并

封装一个函数来实现所有的中间件合并

七、React中的state如何管理

目前主要有三种状态管理方式:

  • 方式一:组件中自己的state管理
  • 方式二:Context数据的共享状态
  • 方式三:Redux管理应用状态

推荐使用 : 

  • UI相关的组件内部可以维护的状态,在组件内部自己来维护
  • 大部分需要共享的状态,都交给redux来管理和维护
  • 从服务器请求的数据(包括请求的操作),交给redux来维护
  • 根据不同的情况会进行适当的调整

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/831851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轻松批量文件改名!一键翻译重命名文件夹/文件,省时高效!」

繁忙的数字时代&#xff0c;我们经常需要处理大量的文件和文件夹。而手动逐个更改文件名不仅费时费力&#xff0c;还容易出错。因此&#xff0c;我们为您带来了一款强大的工具——批量文件改名软件&#xff01;现在&#xff0c;您可以一键翻译重命名文件夹和文件&#xff0c;轻…

【设计模式——学习笔记】23种设计模式——访问者模式Visitor(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入要求传统方案 介绍基本介绍应用场景登场角色尚硅谷版本《图解设计模式》版本 案例实现案例一实现拓展 案例二(个人感觉这个案例较好)实现分析拓展一拓展二拓展三 总结额外知识双重分发 文章说明 案例引入 要求 测评系统需求&#xff1a;将观众分为男人和女人…

MySQL数据库——多表操作

文章目录 前言多表关系一对一关系一对多/多对一关系多对多关系 外键约束创建外键约束插入数据删除带有外键约束的表的数据删除外键约束 多表联合查询数据准备交叉连接查询内连接查询外连接查询左外连接查询右外连接查询满外连接查询 子查询子查询关键字ALL 关键字ANY 和 SOME 关…

2023年华数杯数学建模A题思路代码分析 - 隔热材料的结构优化控制研究

# 1 赛题 A 题 隔热材料的结构优化控制研究 新型隔热材料 A 具有优良的隔热特性&#xff0c;在航天、军工、石化、建筑、交通等 高科技领域中有着广泛的应用。 目前&#xff0c;由单根隔热材料 A 纤维编织成的织物&#xff0c;其热导率可以直接测出&#xff1b;但是 单根隔热…

详解Spring Bean的生命周期

详解Spring Bean的生命周期 Spring Bean的生命周期包括以下阶段&#xff1a; 1. 实例化Bean 对于BeanFactory容器&#xff0c;当客户向容器请求一个尚未初始化的bean时&#xff0c;或初始化bean的时候需要注入另一个尚未初始化的依赖时&#xff0c;容器就会调用createBean进…

剑指Offer 05.替换空格

剑指Offer 05.替换空格 目录 剑指Offer 05.替换空格05.替换空格题目代码&#xff08;容易想到的&#xff09;利用库函数的方法题解&#xff08;时间复杂度更低&#xff09;面试&#xff1a;为什么java中String类型是不可变的 05.替换空格 题目 官网题目地址 代码&#xff08;…

【Python小笔记】零碎同步

1.多字段连接&#xff0c;连接字段名不一致–left_on\right_on对应列示后可匹配 import pandas as pd df_A1pd.read_excel(E:\Mercy\data\mytest\A.xlsx,sheet_name0) df_A2pd.read_excel(E:\Mercy\data\mytest\A.xlsx,sheet_name1)df_Adf_A1.merge(rightdf_A2,howleft,left_o…

P1775 石子合并(弱化版)(内附封面)

石子合并&#xff08;弱化版&#xff09; 题目描述 设有 N ( N ≤ 300 ) N(N \le 300) N(N≤300) 堆石子排成一排&#xff0c;其编号为 1 , 2 , 3 , ⋯ , N 1,2,3,\cdots,N 1,2,3,⋯,N。每堆石子有一定的质量 m i ( m i ≤ 1000 ) m_i\ (m_i \le 1000) mi​ (mi​≤1000)。…

信号执行流程

信号执行是一种用户态与内核态和来回切换&#xff0c;进程不会一接收到信号&#xff0c;就立刻执行&#xff0c;而是在合适的时候执行信号&#xff0c;&#xff08;手头有重要的事情等等再说。 一般来说都是在从内核态返回用户态的时候检测是否有可执行的信号&#xff08;可执…

超详细|ChatGPT辅助论文降重教程100%降至13%

本文讲述使用ChatGPT对论文进行辅助降重&#xff0c;鼓励大家解放大脑&#xff0c;多思考核心论点 祝看到本教程的小伙伴们都完成论文&#xff0c;顺利毕业。 可以加QQ群交流&#xff0c;一群&#xff1a; 123589938 第一章 ChatGPT指令 1.1 同义词替换 对比分析&#xff0c;…

【MySQL】删除重复数据,先进先删

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…

C语言之结构体篇(简)

结构体 结构体的认知结构体的声明一般声明特殊声明匿名结构体类型 结构体自引用结构体变量的定义与初始化结构体变量的定义结构体变量的初始化 结构体传参结构体内存对齐位段位段声明位段的内存分配位段跨平台问题: 结构体是由我们自己创造的一种类型&#xff0c;使得C语言有能…

kafka权威指南(阅读摘录)

零复制 Kafka 使用零复制技术向客户端发送消息——也就是说&#xff0c;Kafka 直接把消息从文件&#xff08;或者更确切地说是 Linux 文件系统缓存&#xff09;里发送到网络通道&#xff0c;而不需要经过任何中间缓冲区。这是 Kafka 与其他大部分数据库系统不一样的地方&#…

归并排序——“数据结构与算法”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容仍然是数据结构与算法专栏的排序呀&#xff0c;下面&#xff0c;让我们进入归并排序的世界吧&#xff01;&#xff01;&#xff01; 归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种…

[腾讯云 Cloud studio 实战训练营] 制作Scrapy Demo爬取起点网月票榜小说数据

首语 最近接触到了一个关于云开发的IDE&#xff0c;什么意思呢&#xff1f; 就是我们通常开发不是在电脑上吗&#xff0c;既要下载编译器&#xff0c;还要下载合适的编辑器&#xff0c;有的时候甚至还需要配置开发环境&#xff0c;有些繁琐。而这个云开发的IDE就是只需要一台…

“苏豪 x 莱佛士”再度携手,惊艳亮相上海发型师节!

2023年6月28日&#xff0c;以“为爱启航”为主题的第16届AHF亚洲发型师节在上海跨国采购中心盛大开幕。继上次在施华蔻专业2023春夏新季风发布会上&#xff0c;苏豪x莱佛士合作的大秀&#xff0c;赢得了现场观众阵阵掌声。这次“Kraemer苏豪x莱佛士”再度携手&#xff0c;惊艳亮…

PoseiSwap:基于 Nautilus Chain ,构建全新价值体系

在 DeFi Summer 后&#xff0c;以太坊自身的弊端不断凸显&#xff0c;而以 Layer2 的方式为其扩容成为了行业很长一段时间的叙事方向之一。虽然以太坊已经顺利的从 PoW 的 1.0 迈向了 PoS 的 2.0 时代&#xff0c;但以太坊创始人 Vitalik Buterin 表示&#xff0c; Layer2 未来…

MySQL常见问题处理(三)

MySQL 常见问题解决 夕阳留恋的不是黄昏&#xff0c;而是朝阳 上一章简单介绍了MySQL数据库安装(二), 如果没有看过, 请观看上一章 一. root 用户密码忘记&#xff0c;进行重置操作 复制内容来源链接: https://blog.csdn.net/weixin_48927364/article/details/123556927 一.…

【MFC]实现16进制文件浏览器-详细步骤+代码

学习MFC已经两天了&#xff0c;我们来写一个小项目&#xff1a;16进制文件浏览器&#xff0c;简单实现&#xff0c;因为我们MFC学的还不是很透彻&#xff0c;这里会给出详细的每一个步骤&#xff0c;并且详细解释每一个方法&#xff1a; 文章目录 提前了解步骤&#xff1a;基本…

Apache poi 对单元格进行合并

需求背景: 在导出excel时, 需要对内容相同的单元格进行纵向合并 期望达到的效果: poi 实现合并单元格的方法 sheet.addMergedRegion(new CellRangeAddress(开始行, 结束行, 开始列, 结束列)); 个人的实现思路: 1): 举个列子, 就拿截图贴出的 [公司类别] 这一列来进行说明 …