Redux

Redux中文官网:www.redux.org.cn/

一. 什么是Redux

用于JavaScript应用的可预测状态容器。 可以帮助你开发出行为稳定可预测的、运行于不同的环境(客户端、服务器、原生应用)、易于测试的应用程序。

状态(State)

state直译过来就是状态,如果使用过React,对于state应该非常熟悉。state不过就是一个变量,一个用来记录(组件)状态的变量。组件可以根据不同的状态值切换为不同的显示,比如,用户登录和没登录看到页面应该是不同的,那么用户的登录与否就应该是一个状态。再比如,数据加载与否,显示的界面也应该不同,那么数据本身就是一个状态。换句话说,状态控制了页面的如何显示。

但是需要注意的是,状态并不是React中或其他类似框架中独有的。所有的编程语言,都有状态,所有的编程语言都会根据不同的状态去执行不同的逻辑,这是一定的。所以状态是什么,状态就是一个变量,用以记录程序执行的情况。


www.redux.org.cn/tutorials/f...

容器(Container)

容器当然是用来装东西的,状态容器即用来存储状态的容器。状态多了,自然需要一个东西来存储,但是容器的功能却不是仅仅能存储状态,它实则是一个状态的管理器,除了存储状态外,它还可以用来对state进行查询、修改等所有操作。(编程语言中容器几乎都是这个意思,其作用无非就是对某个东西进行增删改查)

可预测(Predictable)

可预测指我们在对state进行各种操作时,其结果是一定的。即以相同的顺序对state执行相同的操作会得到相同的结果。简单来说,Redux中对状态所有的操作都封装到了容器内部,外部只能通过调用容器提供的方法来操作state,而不能直接修改state。这就意味着外部对state的操作都被容器所限制,对state的操作都在容器的掌控之中,也就是可预测。

总的来说,Redux是一个稳定、安全的状态管理器。

二. 为什么是Redux

问:不对呀?React中不是已经有state了吗?为什么还整出一个Redux来作为状态管理器呢?

答:state应付简单的值还可以,如果值比较复杂的话并不是很方便。

问:复杂的值可以用useReducer嘛!

答:的确可以啊!但无论是state还是useRuducer,state在传递起来还是不方便,至上至下一层层的传递并不方便。

问:那不还是有context吗?

答:的确使用context可以解决state的传递问题,但依然是简单的数据尚可,如果数据结构过于复杂会使得context变得异常的庞大,不方便维护。

Redux可以理解为是reducer和context的结合体,使用Redux即可管理复杂的state,又可以在不同的组件间方便的共享传递state。当然,Redux主要使用场景依然是大型应用,大型应用中状态比较复杂,如果只是使用reducer和context,开发起来并不是那么的便利,此时一个有一个功能强大的状态管理器就变得尤为的重要。

三. 图解Redux

我们可以使用一张 gif 图来图解 Redux 应用中的数据流。

  • actions 会在用户交互如点击时被 dispatch
  • store 通过执行 reducer 方法计算出一个新的 state
  • UI 读取最新的 state 来展示最新的值

四. 编写Redux代码

Redux 核心库
npm install redux
Redux Toolkit
bash 复制代码
npm install @reduxjs/toolkit

Redux Toolkit (RTK) 是我们Redux官方推荐的编写 Redux 逻辑的标准方式。

RTK 包含了有助于简化许多常见场景的工具,包括 配置 Store创建 reducer 并编写 immutable 更新逻辑, 甚至还包含 一次性创建整个 State 的 "Slice"

1.设置 Store

使用 configureStore

Redux Toolkit 的 configureStore API,可简化 store 的设置过程configureStore 包裹了 Redux 核心 createStore API,并自动为我们处理大部分 store 设置逻辑。事实上,我们可以有效地将其缩减为一步:

typescript 复制代码
import { configureStore } from '@reduxjs/toolkit';
import { formReducer } from './formSlice';
import { errorReducer } from './errorSlice';

const store = configureStore({
  reducer: {
    formInfo: formReducer,
    errorInfo: errorReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

configureStore 为我们完成了所有工作:

  • formReducererrorReducer 组合到根 reducer 函数中,它将处理看起来像 {form, error} 的根 state
  • 使用根 reducer 创建了 Redux store
  • 自动添加 middleware redux-toolkit.js.org/rtk-query/o...
  • 自动设置 Redux DevTools 扩展连接

使用 createSlice

createSlice 接收一个包含三个主要选项字段的对象:

  • name:一个字符串,将用作生成的 action types 的前缀
  • initialState:reducer 的初始 state
  • reducers:一个对象,其中键是字符串,值是处理特定 actions 的 "case reducer" 函数 让我们先看一个独立的小示例。
javascript 复制代码
import { createSlice } from '@reduxjs/toolkit';
const formSlice = createSlice({
  name: 'userInfo',
  initialState: {
    userName: '',
    age: null,
    email: ''
  },
  reducers: {
    setName(state, action) {
      console.log(action);

      state.userName = action.payload;
    },
    setAge(state, action) {
      state.age = action.payload;
    },
    setEmail(state, action) {
      state.email = action.payload;
    }
  }
});

export const { setName, setAge, setEmail } = formSlice.actions;
export const { reducer: formReducer } = formSlice;
  • 我们在 reducers 对象中编写 case reducer 函数,并赋予它们高可读性的名称
  • createSlice 会自动生成对应于每个 case reducer 函数的 action creators
  • createSlice 在默认情况下自动返回现有 state
  • createSlice 允许我们安全地"改变"(mutate)state!
    生成的 action creators 我们可以单独解构和导出它们。
    完整的 reducer 函数可以作为 slice.reducer 使用,和之前一样,我们通常会 export default slice.reducer。 那么这些自动生成的 action 对象是什么样的呢?让我们调用并打印其中一个 action 来看下:
lua 复制代码
console.log(action);
// {type: 'userInfo/setName', payload: 'xiaoming'}

createSlice 通过将 slice 的name 字段与 reducer 函数的 setName 名称相结合,为我们生成了 action type 字符串。默认情况下,action creator 接收一个参数,并将其作为 "action.payload" 放入 action 对象中。

在生成的 reducer 函数内部,createSlice 将检查 dispatch action 的 action.type 是否与它生成的名称之一相匹配。如果匹配成功,它将运行那个 case reducer 函数。

2.使用 Provider 透传 Store

现在组件可以从 store 中读取 state,并 dispatch action 到 store。但是仍少了点什么。比如 React-Redux hook 在哪里以及如何找到正确的 Redux store?hook 仅仅是一个 JS 函数,它并不能从 store.js 中自动导入 store。

我们必须明地确告诉 React-Redux 当前组件需要的 store。为此,我们使用 <Provider> 组件包裹 <App> 组件,并将 Redux store 作为 prop 传递给 <Provider> 组件。之后,应用程序中的每个组件都可以在需要时能够访问到 Redux store。

javascript 复制代码
import { Provider } from 'react-redux';
import store from './examples/store/store';
import ErrorMessage from '@/app/examples/errorMessage/page';
export default function RootLayout({
  children
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <link rel="stylesheet" href="https:****" />
      </head>
      <body>
        <Provider store={store}>
          <ErrorMessage />
          {children}
        </Provider>
      </body>
    </html>
  );
}

3.使用 useSelector 从 Store 中读取 State

你应该熟悉类似 useState 的 React hook,在 React 函数组件中可以通过调用它们来访问 React state 值。

和许多其他库一样,React-Redux 也有它的自定义 hook,你可以直接在组件中使用它们。React-Redux hook 使 React 组件能够通过读取 state 以及 dispatch action 来和 Redux store 进行交互。 我们将看到的第一个 React-Redux hook 是 useSelector,它使得 React 组件可以从 Redux store 中读取数据

useSelector 接收一个 selector 函数。selector 函数接收 Redux store 的 state 作为其参数,然后从 state 中取值并返回

javascript 复制代码
'use client';

import { useSelector } from 'react-redux';
import { RootState } from '../store/store';
export default function UserInfo() {
  const formInfo = useSelector((state: RootState) => state.formInfo);

  return (
    <div className="hook-form">
      <div className="list-form-lg">
        <div className="field" role="group" aria-labelledby="legend5">
          <label className="field-label" id="legend5">
            UserName
          </label>
          <div className="field-control">{formInfo.userName}</div>
        </div>
        <div className="field" role="group" aria-labelledby="legend5">
          <label className="field-label" id="legend5">
            Age
          </label>
          <div className="field-control">{formInfo.age}</div>
        </div>
        <div className="field" role="group" aria-labelledby="legend5">
          <label className="field-label" id="legend5">
            Email
          </label>
          <div className="field-control">{formInfo.email}</div>
        </div>
      </div>
    </div>
  );
}

<UserInfo> 组件第一次渲染时,useSelector hook 会调用 (state: RootState) => state.formInfo 并传入 全部的 Redux state 对象。无论 selectTodos 返回什么,useSelector 都会把它返回给组件。因此,组件中的 const formInfo 最终会和 Redux store state 中的 state.formInfo 数组保持一致。

假设我们 dispatch {type: 'userInfo/setName'} 这个 action,将会发生什么?Redux state 会被 reducer 更新,但是组件没有感知到变化并用新值重新渲染。

虽然 可以 调用 store.subscribe() 来监听每个组件中 store 的变化,但是这样会变得重复且难以控制。

    1. 灵活性store.subscribe() 可以用于任何环境,不仅限于 React 应用。
    1. 性能开销store.subscribe() 会在每次 state 更新时触发回调,这可能导致不必要的性能开销,尤其是在大型应用中。
    1. 复杂性:手动管理订阅和取消订阅可能会增加代码的复杂性。

幸运的是,**useSelector 会自动订阅 Redux store!**这样,任何时候 dispatch action,它都会立即再次调用对应的 selector 函数。如果 selector 返回的值与上次运行时相比发生了变化,useSelector 将强制组件使用新值重新渲染 。我们仅需要在组件中调用一次 useSelector() 即可。

4.使用 useDispatch 来 Dispatch Action

组件怎么向 store dispatch action 呢? React-Redux 的 useDispatch hook 函数会返回 store 的 dispatch 方法。(事实上这个 hook 的内部实现真的是 return store.dispatch。)

因此,我们可以在任何需要 dispatch action 的组件中使用 const dispatch = useDispatch(),然后根据需要调用 dispatch(someAction)

相关推荐
CodeToGym1 天前
使用 Vite 和 Redux Toolkit 创建 React 项目
前端·javascript·react.js·redux
@PHARAOH24 天前
HOW - React 状态模块化管理和按需加载(一) - react-redux
前端·javascript·react.js·redux
getaxiosluo1 个月前
react搭建router,redux教程
前端·react.js·arcgis·redux·router
学前端的小朱2 个月前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
少恭写代码2 个月前
duxapp放弃了redux,在duxapp中局部、全局状态的实现方案
react native·taro·redux·duxapp
且陶陶º5 个月前
【案例】使用React+redux实现一个Todomvc
javascript·react.js·redux
Orzak6 个月前
[译]全栈Redux实战
redux
摇光938 个月前
React + 项目(从基础到实战) -- 第十期
前端·javascript·react.js·redux
石小石Orz8 个月前
不要滥用Pinia和Redux了!多组件之间交互可以手写一个调度器!
前端·vuex·redux
豆浆不好喝9 个月前
深入浅出之redux-thunk
react.js·redux