什么是Redux

Redux 是用于js应用的状态管理库,通常和React一起用。帮助开发者管理应用中各个组件之间的状态,使得状态的变化变得更加可预测和易于调试。
Redu也可以不和React组合使用。(通常一起使用)

Redux 三大原则

单一数据源

  • 整个应用程序的state被存储在一棵obj tree中,这个obj tree只存储在一个store中;
  • redux 并没有强制让我们不能创建多个Store,但这样做不利于数据的维护;
  • 单一的数据源可以让整个应用程序的state变得方便维护,追踪,修改。

State是只读的

  • 唯一修改state的方法是触发action
  • 这样确保了View或网络请求都不能直接修改state,他们只能通过action来描述自己想要如何修改state;
  • 可以保证所有修改都被集中化处理,并按照严格的顺序来执行,所以不必担心race condition的问题。

使用纯函数来执行修改

  • 通过reducer将旧state和actions联系在一起,返回一个新的State
  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree 的一部分
  • 但所有的reducers都应该是纯函数,不能产生任何的副作用。

redux 如何使用

  1. 安装react-redux:
    yarn add react-redux
  2. 创建store 管理全局状态

src/store/constants.js

export const ADD_NUMBER = 'add_number'
export const SUB_NUMBER = 'sub_number'
export const CHANGE_BANNERS = 'change_banners'
export const CHANGE_RECOMMENDS = 'change_recommends'

创建reducer管理状态

src/store/reducers.js

import * as actionTypes from "./constants"

const initialState = {
  counter: 100,
  banners: [],
  recommends: []
}
function reducer(state = initialState, action) {
  switch (action.type) {
    case actionTypes.ADD_NUMBER:
      return { ...state, counter: state.counter + action.num }
    case actionTypes.SUB_NUMBER:
      return { ...state, counter: state.counter - action.num }
    case actionTypes.CHANGE_BANNERS:
      return { ...state, banners: action.banners }
    case actionTypes.CHANGE_RECOMMENDS:
      return { ...state, recommends: action.recommends }
    default:
      return state
  }
}
export default reducer

src/store/index.js

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

const store = createStore(reducer);

export default store;

src/store/actionCreators.js
创建actionCreators,放修改状态的函数

import * as actionTypes from "./constants"
import axios from "axios"

export const addNumberAction = (num) => ({
  type: actionTypes.ADD_NUMBER,
  num
})

export const subNumberAction = (num) => ({
  type: actionTypes.SUB_NUMBER,
  num
})


export const changeBannersAction = (banners) => ({
  type: actionTypes.CHANGE_BANNERS,
  banners
})

export const changeRecommendsAction = (recommends) => ({
  type: actionTypes.CHANGE_RECOMMENDS,
  recommends
})



export const fetchHomeMultidataAction = () => {
  // 如果是一个普通的action, 那么我们这里需要返回action对象
  // 问题: 对象中是不能直接拿到从服务器请求的异步数据的
  // return {}

  return function(dispatch, getState) {
    // 异步操作: 网络请求
    // console.log("foo function execution-----", getState().counter)
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const banners = res.data.data.banner.list
      const recommends = res.data.data.recommend.list

      // dispatch({ type: actionTypes.CHANGE_BANNERS, banners })
      // dispatch({ type: actionTypes.CHANGE_RECOMMENDS, recommends })
      dispatch(changeBannersAction(banners))
      dispatch(changeRecommendsAction(recommends))
    })
  }

  // 如果返回的是一个函数, 那么redux是不支持的
  // return foo
}
  1. 在项目index.js 根节点引用
    src/index.js
import {Provider} from 'react-redux'
import store from 'react-redux'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
	<React.StrictMode>
		<Provider store={store}>
			<App />
		</Provider>
	</React.StrictMode>
);
  1. 在需要使用redux的页面或组件中,通过connect高阶组件映射到该组件的props中,解耦store和class组件的耦合。

redux中异步操作

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

  • 目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码
  • 比如日志记录,调用异步接口,添加代码调试功能等

发送异步网络请求,可以添加对应的中间件

  • 官网推荐 redux-thunk

redux-thunk 如何可以发送异步请求

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

如何使用redux-thunk

  1. 安装redux-thunk
    yarn add redux-thunk
  2. 创建store时传入应用了middleware的enhance函数
  • 通过applyMiddleware来结合多个Middleware,返回一个enhancer;
  • 将enhancer作为第二个参数传入到createStore中;
// 通过applyMiddleware来结合多个Middleware, 返回一个enhancer
const enhancer = applyMiddleware(thunkMiddleware);
// 将enhancer作为第二个参数传入到createStore中
const store = createStore(reducer, enhancer);
  1. 定义返回一个函数的action:
  • 注意:这里返回一个函数
  • 该函数在dispatch之后会被执行
const getHomeMultidataAction = () => {
  return (dispatch) => {
    axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
      const data = res.data.data;
      dispatch(changeBannersAction(data.banner.list));
      dispatch(changeRecommendsAction(data.recommend.list));
    })
  }
}

combineReducers函数

  • 事实上,redux给我们提供了一个combineReducers函数可以让我们方便对多个reducer进行合并
  • 那么combineReducers是如何实现的?
  • 它也是将我们传入的reducers合并到一个对象中,最终返回一个combination函数(相当于我们之前的reducer函数)
  • 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state。
  • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新。

Redux 基本原理

所有的状态都以对象树的方式(state)存放于单个store中。

唯一改变状态树(state tree)的方法是创建action:一个描述发生了什么的对象,并将其dispatch派发给store。要指定状态树如何响应action来进行更新,你可以编写纯reducer函数,这些函数根据旧的state和action来计算新state。

新的state被创建后,对象会自动传递给所有注册了监听器的组件,从而触发组件的重新渲染,使得界面始终保持与当前的state对象一致。

Redux 使用及基本原理_应用程序


Redux 使用及基本原理_ide_02

Redux在React中具体的使用方法

官方建议,安装其他两个插件 Redux Toolkit和React-Redux

  1. React Toolkit(RTK):官方推荐编写Redux逻辑的方式,是一套工具的集合,简化书写方式
  2. React-Redux:用来链接 Redux和React组件的中间件
  3. 安装方式
    npm install @reduxjs/toolkit react-redux

Redux Toolkit(RTK)

1. createSlice 函数

作用:创建一个Redux的slice。它接受一个包含reducer函数,slice名称和初始状态的配置对象,并返回一个包含reducer和action creators的对象。
参数

  • name:slice的名称,用于标识状态的一部分。
  • initialState:slice的初始状态,定义了状态的初始值‘
  • reducers:一个对象,包含一组同步的reducer函数,用于更新状态。
    返回值:
    createSlice 返回一个包含以下属性的对象:
  • name:slice的名称
  • reducer:一个reducer 函数,用于处理来自action creators的动作并更新状态
  • actions:一组action creators,用于创建派发给reducer的动作。

栗子 🌰:

import { createSlice } from '@reduxjs/toolkit';

// 定义初始状态
const initialState = {
  count: 0,
};

// 创建一个 Redux slice
const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    // 定义同步的 reducer 函数
    increment(state) {
      state.count += 1;
    },
    decrement(state) {
      state.count -= 1;
    },
    // 可以接受额外参数的 reducer 函数
    incrementByAmount(state, action) {
      state.count += action.payload;
    },
  },
});

// 导出action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出reducer
export default counterSlice.reducer;

上述代码使用 createSlice 函数创建一个名为 counter 的slice。包含一个名为 count的状态和三个同步的reducer函数:increment,derement和incrementByAmount。

  • 通过increment,decrement,incrementByAmount 派发动作
    通过counterSlice.reducer 处理动作

configureStore 函数

作用:创建一个Redux store,它接受一个包含reducer函数和其他配置选项的对象,并返回一个Redux store 实例。
参数

  • reducer:一个或多个reducer函数,用于处理来自action creators 的动作并更新状态
  • 其他配置选项:包括 middleware,devTools 等,用于配置store的行为。
    返回值
  • configureStore 返回一个Redux store 实例,它包含以下属性和方法:
  • getState():用于获取当前的状态
  • dispatch(action):用于派发一个动作,以触发状态的更新
  • subscribe(listener):用于添加一个状态变化的监听器,当状态发生变化时会被调用
  • replaceReducer(nextReducer):用于替换当前的reducer

栗子🌰:

import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers'; // 导入根 reducer

// 创建 Redux store
const store = configureStore({
  reducer: rootReducer,
  // middleware: getDefaultMiddleware => getDefaultMiddleware(), // 使用默认的中间件
  // devTools: process.env.NODE_ENV !== 'production', // 在开发环境启用 Redux DevTools
});

export default store

在栗子中,我们使用configureStore 函数创建了一个Redux store
我们传入了一个根reducer rootReducer,它是一个包含所有reducer的对象。我们还配置了默认的中间件,并在开发环境下启用了Redux DevTools。

react-redux

它将所有组件分为两大类:UI 组件和容器组件。

  1. UI 组件:负责呈现页面(React)
  2. 容器组件:负责管理数据和业务逻辑(Redux)

react-redux中常用的组件及方法

Provider 组件

作用:将Redux的store 传递给整个React应用程序,使得所有组件都能够访问到redux的状态。通过provider,我们可以在任何地方使用redux的状态和派发动作。

好处:在整个应用程序中,任何一个组件都可以通过connect函数或useSelector钩子函数来访问Redux store 中的状态,而不需要手动将store传递给每一个组件。

  • 简化代码:不需要在每一个组件中手动传递store,通过Provider,store可以在整个应用程序中自动传递给需要的组件。
  • 避免prop drilling:避免了在组件层级结构中进行多层次的prop 传递,提高了代码的可维护性和可读性。
  • 一致性:所有的组件都使用相同的redux store,保证了应用程序状态的一致性。
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

import store from './store';
import { Provider } from 'react-redux';

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <Provider store={store}>
      <App />
    </Provider>
)

参考