Redux 和 Redux Toolkit 入门

什么是 Redux?

正如官方文档所述:Redux 是一种用于管理和更新全局应用程序状态的模式和库。它充当需要在整个应用程序中使用的状态的集中存储,其规则确保只能以可预测的方式更新状态(不会引起副作用或意外行为)。

因此,Redux 作为应用程序的全局状态管理器,确保状态的每个变化都经过特定且有组织的过程。

将状态视为需要在应用程序的所有组件之间共享的 JavaScript 对象。

如果您有这样的用户对象:

const user = {
  username: "Jane Doe",
  email: "email@example.com",
};

如果您手动将此对象传递到应用的组件树中,则与每个组件共享此对象很快就会变得混乱。这时 Redux 就派上用场了。

Redux 的工作原理

Redux 有自己的一套术语和规则,每个组件在访问或更新数据时都应遵循这些术语和规则。最基本的是“actions”和“dispatch”。

Redux 中的操作描述了组件想要对状态执行的操作。例如:

  • 它是否想更新用户的用户名?
  • 更改他们的电子邮件?
  • 当用户注销并且不再需要该数据时,清除用户的数据?
  • 这些任务可以转换为 Redux 可以理解和处理的“动作”类型。

    定义动作

    动作是简单的 JavaScript 对象,包含类型(标识动作的字符串)和可选的有效负载(用于更新状态的数据)。

    以下是一些示例操作:

    const emailUpdated = {
      type: "user/emailUpdated",
      payload: "email@main.co",
    };
    
    const usernameUpdated = {
      type: "user/usernameUpdated",
      payload: "Hajar",
    };
    
    const clearUserData = {
      type: "user/clearUserData",
    };

    定义这些之后,组件就可以使用它们来指定想要对状态进行什么样的改变。

    现在组件知道它们需要执行哪些“动作”以及要包含哪些数据/有效负载,因此它们需要将它们“分派”到 Redux 存储。

    Dispatch 函数:向 Redux 发送 Action

    `dispatch` 函数用于将 `actions` 发送到 Redux store。它是一个常规的 JavaScript 函数,唯一的区别是:

  • Redux 确切地知道调度函数是什么,以及在组件内调用它时要做什么。
  • 该函数接受动作对象,该对象必须包含类型(标识动作的字符串)。
  • 当你使用 `action` 调用 `dispatch` 时,Redux 会处理它,然后触发处理它的正确的 `reducer`。

    什么是 Reducer?

    `reducer` 是一个 Redux 函数,它接受当前 `state` 和 `action`,根据 `action` 类型确定如何更新 `state`,然后返回更新后的状态。

    例如,如果 Reducer 收到“user/usernameUpdated”之类的操作,它会使用操作负载中的值修改状态中的用户名。一旦 Reducer 更新状态,Redux 就会通知相关组件重新渲染。

    瞧!组件现在具有更新的用户数据,而不必担心 Redux 为实现这一目标所采取的步骤。

    以下是 `reducer` 的示例:

    const ACTION_TYPES = {
      UPDATE_USERNAME: "user/usernameUpdated",
      UPDATE_EMAIL: "user/emailUpdated",
      CLEAR_STATE: "user/userDataCleared",
    };
    
    const initialState = {
      username: "",
      email: "",
    };
    
    const userReducer = (state = initialState, action) => {
      switch (action.type) {
        case ACTION_TYPES.UPDATE_USERNAME:
          return {
           // spread the state to return a new state object 
            ...state,
            username: action.payload,
          };
        case ACTION_TYPES.UPDATE_EMAIL:
          return {
            ...state,
            email: action.payload,
          };
        case ACTION_TYPES.CLEAR_STATE:
          return initialState;
        default:
          return state;
      }
    };
    
    export default userReducer;

    以下是组件如何“分派”“动作”:

    const updateUsername = (name) => {
      dispatch({
        type: "user/usernameUpdated",
        payload: name,
      });
    };
    
    const updateEmail = (email) => {
      dispatch({
        type: "user/emailUpdated",
        payload: email,
      });
    };
    
    const clearUserData = () => {
      dispatch({
        type: "user/userDataCleared",
      });
    };

    这很酷,但是你有没有注意到我们为这样一个简单的 Reducer 示例写了多少代码?

  • 我们必须定义所有动作并确保它们在组件中得到正确使用。
  • 我们必须为每个动作创建一个 switch case 块。
  • 我们必须记住在更新电子邮件或用户名之前传播之前的状态。(这是首先将旧状态复制到新对象并返回新状态 - Reducer 需要是纯函数)。
  • 因此,您可以想象,在实际应用中引入“reducers”和“actions”需要编写多少代码。这时 Redux ToolKit 就派上用场了。

    将 Redux Toolkit 与 Next.js 结合使用

    要在 Next.js 项目中使用 Redux Toolkit,我们需要安装 Next.js:

    npx create-next-app@latest

    然后,安装 `@reduxjs/toolkit` 和 `react-redux`:

    npm install @reduxjs/toolkit react-redux

    **配置 Redux Store**

    接下来,创建一个文件“app/redux/store.ts”来配置Redux存储。我们将使用Redux Toolkit中的“configureStore”API。

    import { configureStore } from '@reduxjs/toolkit';
    
    export default configureStore({
      // The reducer will be added here later
      reducer: {}
    });

    使用 Redux Provider 包装应用程序

    为了使您的 Redux 存储在整个应用程序中可访问,请使用“react-redux”中的“Provider”组件包装您的根组件,并将“store”传递给它。

    "use client";
    
    import { Provider } from "react-redux";
    import store from "@/app/redux/store";
    import MainContent from "@/app/components/MainContent";
    
    export default function Home() {
      return (
        
          
        
      );
    }

    使用 Redux Toolkit 设置切片

    在 Redux Toolkit 中,切片管理“状态”和“reducers”。要创建切片,请使用“@reduxjs/toolkit”中的“createSlice”函数。

    import { createSlice } from "@reduxjs/toolkit";
    
    const initialState = {
      username: "",
      email: "",
    };
    
    export const userSlice = createSlice({
      name: "user",         
      initialState,       
      reducers: {           
        usernameUpdated: (state, action) => {
          state.username = action.payload;
        },
        emailUpdated: (state, action) => {
          state.email = action.payload;
        },
        userDataCleared: (state) => {
          state.username = "";
          state.email = "";
        },
      },
    });
    
    export default userSlice.reducer;

    `createSlice` 接受的主要参数是:

  • 名称:切片的唯一名称。
  • initialState:切片的初始状态。
  • Reducers:Reducer 函数用于处理状态的更新。
  • **注意**:你有没有注意到我们在这里是如何直接修改状态的?在 `createSlice` 中,我们可以使用该语法来简化更新状态的逻辑 - 特别是对于深度嵌套的值。然而,在底层,事情是这样的:

    Redux Toolkit 允许我们在 Reducer 中编写“改变”逻辑。它实际上不会改变状态,因为它使用了 Immer 库,该库会检测“草稿状态”的更改并根据这些更改生成全新的不可变状态。

    但是等等,我们如何检查“action”类型并将它们映射到“reducers”,就像我们在前面的代码中所做的那样?这里有个很酷的部分:你不需要!一旦你定义了“reducers”,Redux Toolkit 会自动为每个动作生成动作创建器,我们可以轻松地从切片文件中导出它们,如下所示:

    // app/redux/userSlice.ts
    import { createSlice } from "@reduxjs/toolkit";
    
    
    const initialState = {
      username: "",
      email: "",
    };
    
    
    export const userSlice = createSlice({
      name: "user",
      initialState,
      reducers: {
        usernameUpdated: (state, action) => {
          state.username = action.payload;
        },
        emailUpdated: (state, action) => {
          state.email = action.payload;
        },
        userDataCleared: (state) => {
          state = initialState;
        },
      },
    });
    
    
    // the actions are exported to be used by any component now
    export const { usernameUpdated, emailUpdated, userDataCleared } =
      userSlice.actions;
    
    
    export default userSlice.reducer;

    然后我们可以使用 `userSlice.reducer` 来更新 `store`:

    import { configureStore } from "@reduxjs/toolkit";
    import userReducer from "./userSlice";
    
    export default configureStore({
      reducer: {
        user: userReducer,
        // we can inroduce more reducers from different slices here
        // posts: postsReducer
      },
    });

    使用选择器和调度动作

    现在已经设置了存储和切片,我们可以使用“选择器”来获取数据并分派操作来更新它。

    `selectors` 用于访问状态数据并使其可供应用程序的组件使用。以下是在 `userSlice` 文件中创建和导出它们的方法:

    export const selectUsername = (state) => state.user.username;
    export const selectEmail = (state) => state.user.email;

    现在我们可以使用选择器数据和分派操作:

    import { useSelector, useDispatch } from "react-redux";
    import { selectUsername, selectEmail } from "@/redux/userSlice";
    import { usernameUpdated, emailUpdated, userDataCleared } from "@/redux/userSlice";
    
    const UserComponent = () => {
      const username = useSelector(selectUsername);
      const email = useSelector(selectEmail);
      const dispatch = useDispatch();
    
      const handleUpdateUsername = (name: string) => dispatch(usernameUpdated(name));
      const handleUpdateEmail = (email: string) => dispatch(emailUpdated(email));
      const handleClearUserData = () => dispatch(userDataCleared());
    
      return (
        

    Username: {username}

    Email: {email}

    ); };