⚗️ 面向初学者的 React Redux CRUD 应用程序 [使用 Hooks] 内容 Redux 与 React Context API 和 useReducer Redux 的优势 🔨 1. 设置 👪 2. 从状态加载用户 ➕ 3. 添加新用户 🔧 4. 编辑用户 🗑️ 5. 删除用户 ✨ 6. 异步加载新用户

2025-05-26

⚗️ 面向初学者的 React Redux CRUD 应用 [带 Hooks]

内容

Redux 与 React Context API 以及 useReducer

Redux 的好处

🔨 1. 设置

👪 2. 从状态加载用户

➕ 3. 添加新用户

🔧 4. 编辑用户

🗑️ 5. 删除用户

✨6.异步加载新用户

Redux 一直是 React 应用程序中管理状态的最常用库。它有很多优点,但对于 React 初学者来说,学习起来可能有些困难。在本教程中,我们将使用 React 和 Redux 构建一个简单的 CRUD 应用。

在此处查看已完成的应用程序
在此处查看代码

内容

  1. 🔨 设置
  2. 👪 从状态加载用户
  3. ➕ 添加新用户
  4. 🔧 编辑用户
  5. 🗑️ 删除用户
  6. ✨ 异步加载新用户

Redux 与 React Context API 以及 useReducer

上周我写了一篇关于如何使用 React 的 Context API 和 useReducer hook来管理状态的教程。这两者的组合非常棒,在我看来,应该用于状态逻辑不太复杂的中小型应用程序。当你的应用程序规模扩大,或者你想为此做好充分准备时,建议切换到 Redux。

Redux 的好处

为什么要添加另一个库来管理状态?我以为 React 已经管理状态了?没错,但想象一下,你有很多组件和页面,它们都需要从不同的 API 和数据源获取数据,并管理用户与这些数据和界面交互的状态。你的应用状态很快就会变得一团糟。我发现的主要好处是:

  • 全局状态:Redux 将所有状态保存在一个存储库中,即唯一的事实来源。
  • 可预测:使用单一存储,您的应用程序在将当前状态和操作与应用程序的其他部分同步时几乎没有问题。
  • 可维护性:由于 Redux 对于如何构建代码有严格的指导方针,因此您的代码将更易于维护。

让我们开始吧!

🔨 1. 设置

让我们首先使用默认配置创建一个新的 React 应用程序:
$ npx create-react-app redux-crud-app

首先,让我们删除 /src 文件夹中除 App.js 和 index.js 之外的所有文件。清除 App.js,现在我们只返回一个单词。使用 运行应用程序$ npm run start

App.js

function App() {
  return (
    <h1>Hi</h1>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

让我们添加一个简单的 CSS 库,让我们的应用看起来更美观。在本教程中,我将使用 Skeleton CSS。只需转到 index.html 并在结束标签前添加以下行:
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" />

现在文本应该在本地主机上设置好了样式。让我们添加标准的 React 路由器包作为开发依赖项,以处理不同的页面:

$ npm install react-router-dom --save

App.js

import { Route, BrowserRouter as Router, Switch } from "react-router-dom";

import React from "react";
import { UserList } from "./features/users/UserList";

export default function App() {
  return (
    <Router>
      <div>
        <Switch>
          <Route path="/">
            <UserList />
          </Route>
          <Route path="/add-user">
            <h1>Add user</h1>
          </Route>
          <Route path="/edit-user">
            <h1>Edit user</h1>
          </Route>
        </Switch>
      </div>
    </Router>
  );
}
Enter fullscreen mode Exit fullscreen mode

并为布局添加一个 UsersList 组件:

/features/users/UserList.jsx

export function UserList() {
  return (
    <div className="container">
      <div className="row">
        <h1>Redux CRUD User app</h1>
      </div>
      <div className="row">
        <div className="two columns">
          <button className="button-primary">Load users</button>
        </div>
        <div className="two columns">
          <button className="button-primary">Add user</button>
        </div>
      </div>
      <div className="row">
        <table class="u-full-width">
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Email</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>1</td>
              <td>Dave Gamache</td>
              <td>dave@gmail.com</td>
              <td>
                <button>Delete</button>
                <button>Edit</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

👪 2. 从状态加载用户

首先,我们需要将 redux store 添加到我们的应用中。让我们安装 react-redux 和 redux 工具包:
$ npm install @reduxjs/toolkit react-redux --save

然后创建一个文件 store.js,内容如下:

store.js

import { configureStore } from "@reduxjs/toolkit";

export default configureStore({
  reducer: {},
});
Enter fullscreen mode Exit fullscreen mode

稍后我们将在这里添加 Redux 函数来修改状态(reducer)。现在我们需要使用 Redux 的 Provider 包装器将应用程序包装到 Store 中:

index.js

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

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

接下来,我们添加 redux 状态,并将用户添加到其中。然后,我们将在 UserList 组件中获取此状态。

我们将代码按功能划分。在我们的应用中,我们只有一个功能,即用户。Redux 将每个功能对应的逻辑集合称为切片 (slice)。让我们创建一个:

/功能/用户/用户切片

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

const initialState = [
  { id: "1", name: "Dave Patrick", email: "dave@gmail.com" },
  { id: "2", name: "Hank Gluhwein", email: "hank@gmail.com" },
];

const usersSlice = createSlice({
  name: "users",
  initialState,
  reducers: {},
});

export default usersSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

现在,我们将用户切片(状态的用户部分)添加到 store 中,以便我们可以在应用程序的任何位置访问它。Redux 会自动创建切片的 .reducer 函数。因此,我们将按如下方式添加用户切片:

store.js

import { configureStore } from "@reduxjs/toolkit";
import usersReducer from "./features/users/usersSlice";

export default configureStore({
  reducer: {
    users: usersReducer,
  },
});
Enter fullscreen mode Exit fullscreen mode

我建议使用 Redux DevTools 来查看当前状态及其差异

最后,让我们根据 Redux 状态渲染用户表。要在 Redux 中访问状态,我们必须使用useSelectorhooks。这是一个返回部分状态的函数。我们可以通过传入函数来决定需要哪部分状态。

我们将在状态中获取用户对象。然后我们将该数组渲染为用户列表。

用户列表.jsx

import { useSelector } from "react-redux";

export function UserList() {
  const users = useSelector((state) => state.users);

  return (
    ...
          <tbody>
            {users.map(({ id, name, email }, i) => (
              <tr key={i}>
                <td>{id}</td>
                <td>{name}</td>
                <td>{email}</td>
                <td>
                  <button>Delete</button>
                  <button>Edit</button>
                </td>
              </tr>
            ))}
          </tbody>
    ...
  );
}
Enter fullscreen mode Exit fullscreen mode

这就是我们使用 Redux 在页面上渲染状态的方法,很可行吧?😃

➕ 3. 添加新用户

首先,我们创建一个带有 hooks 的基本表单来管理输入字段。注意,这里我们没有使用 Redux 来管理输入字段的状态。这是因为您不需要把所有东西都放在 Redux 中,实际上,最好只将某个组件需要的状态保存在该组件本身中。输入字段就是一个完美的例子。

/features/users/AddUser.jsx

import { useState } from "react";

export function AddUser() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleName = (e) => setName(e.target.value);
  const handleEmail = (e) => setEmail(e.target.value);

  return (
    <div className="container">
      <div className="row">
        <h1>Add user</h1>
      </div>
      <div className="row">
        <div className="three columns">
          <label for="nameInput">Name</label>
          <input
            className="u-full-width"
            type="text"
            placeholder="test@mailbox.com"
            id="nameInput"
            onChange={handleName}
            value={name}
          />
          <label for="emailInput">Email</label>
          <input
            className="u-full-width"
            type="email"
            placeholder="test@mailbox.com"
            id="emailInput"
            onChange={handleEmail}
            value={email}
          />
          <button className="button-primary">Add user</button>
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

提交时,我们希望将用户添加到状态,并将用户发送回 UserList 组件。如果出现错误,我们将显示错误。

首先,我们向 Redux 用户切片添加一个方法/函数。此方法用于修改状态,Redux 将其称为 Reducer。Reducer 中的方法接收用户状态和操作,在本例中是用户表单字段的值。

Redux 自动为我们创建一个动作,我们可以使用它来调用这个函数。

usersSlice.js

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

const initialState = [
  { id: "1", name: "Dave Patrick", email: "dave@gmail.com" },
  { id: "2", name: "Hank Gluhwein", email: "hank@gmail.com" },
];

const usersSlice = createSlice({
  name: "users",
  initialState,
  reducers: {
    userAdded(state, action) {
      state.push(action.payload);
    },
  },
});

export const { userAdded } = usersSlice.actions;

export default usersSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

要使用此操作函数,我们需要从 Redux 导入 useDispatch hook。我们将检查字段是否为空,然后使用字段来调度 userAdded 操作。为了生成正确的用户 ID,我们获取状态中用户数组的长度并将其加一。

添加用户.jsx

import { nanoid } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
import { useState } from "react";
import { userAdded } from "./usersSlice";

export function AddUser() {
  const dispatch = useDispatch();
  const history = useHistory();

  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [error, setError] = useState(null);

  const handleName = (e) => setName(e.target.value);
  const handleEmail = (e) => setEmail(e.target.value);

  const usersAmount = useSelector((state) => state.users.length);

  const handleClick = () => {
    if (name && email) {
      dispatch(
        userAdded({
          id: usersAmount + 1,
          name,
          email,
        })
      );

      setError(null);
      history.push("/");
    } else {
      setError("Fill in all fields");
    }

    setName("");
    setEmail("");
  };

return (
   ...
   {error && error}
          <button onClick={handleClick} className="button-primary">
            Add user
          </button>
   ...
Enter fullscreen mode Exit fullscreen mode

我们可以将用户添加到商店,太棒了!

🔧 4. 编辑用户

要编辑用户,我们首先通过将编辑按钮链接到 UserList 组件内的动态 /edit-user/{id} 页面来更新它:

<Link to={`/edit-user/${id}`}>
   <button>Edit</button>
</Link>
Enter fullscreen mode Exit fullscreen mode

然后,我们将新的 Reducer 添加到 Redux 切片中。它会在我们的状态中找到用户,如果存在则更新它。

usersSlice.js

const usersSlice = createSlice({
  name: "users",
  initialState,
  reducers: {
    userAdded(state, action) {
      state.push(action.payload);
    },
    userUpdated(state, action) {
      const { id, name, email } = action.payload;
      const existingUser = state.find((user) => user.id === id);
      if (existingUser) {
        existingUser.name = name;
        existingUser.email = email;
      }
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

我们的 EditUser.jsx 文件看起来与 AddUser.jsx 非常相似,只是这里我们使用来自 react-router-dom 的 useLocation 钩子从 URL 路径中获取用户 ID:

EditUser.jsx

import { useDispatch, useSelector } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";

import { useState } from "react";
import { userUpdated } from "./usersSlice";

export function EditUser() {
  const { pathname } = useLocation();
  const userId = pathname.replace("/edit-user/", "");

  const user = useSelector((state) =>
    state.users.find((user) => user.id === userId)
  );

  const dispatch = useDispatch();
  const history = useHistory();

  const [name, setName] = useState(user.name);
  const [email, setEmail] = useState(user.email);
  const [error, setError] = useState(null);

  const handleName = (e) => setName(e.target.value);
  const handleEmail = (e) => setEmail(e.target.value);

  const handleClick = () => {
    if (name && email) {
      dispatch(
        userUpdated({
          id: userId,
          name,
          email,
        })
      );

      setError(null);
      history.push("/");
    } else {
      setError("Fill in all fields");
    }

    setName("");
    setEmail("");
  };

  return (
    <div className="container">
      <div className="row">
        <h1>Edit user</h1>
      </div>
      <div className="row">
        <div className="three columns">
          <label htmlFor="nameInput">Name</label>
          <input
            className="u-full-width"
            type="text"
            placeholder="test@mailbox.com"
            id="nameInput"
            onChange={handleName}
            value={name}
          />
          <label htmlFor="emailInput">Email</label>
          <input
            className="u-full-width"
            type="email"
            placeholder="test@mailbox.com"
            id="emailInput"
            onChange={handleEmail}
            value={email}
          />
          {error && error}
          <button onClick={handleClick} className="button-primary">
            Save user
          </button>
        </div>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

🗑️ 5. 删除用户

我想邀请大家自己来琢磨一下!这是一个很好的练习,可以练习我们之前学到的知识。

以下是我针对减速器的解决方案:

    userDeleted(state, action) {
      const { id } = action.payload;
      const existingUser = state.find((user) => user.id === id);
      if (existingUser) {
        return state.filter((user) => user.id !== id);
      }
    },
Enter fullscreen mode Exit fullscreen mode

您可以在 github 上查看我的代码的完整解决方案。

✨6.异步加载新用户

注意——下面的部分有点棘手,但非常值得学习!

一个不错的功能是从外部 API 加载用户。我们将使用这个免费的 API https://jsonplaceholder.typicode.com/users:。

Redux 本身只能同步运行代码。为了处理异步代码,最常见的方法是使用 redux-thunk,它只是一个简单的函数,允许将异步代码作为操作。

如今,Redux 内置了添加异步代码的功能。许多教程仍在使用 redux-thunk,但configureStoreredux 的新功能已经内置了此功能。

让我们将 API 获取添加到我们的 usersSlice:

export const fetchUsers = createAsyncThunk("fetchUsers", async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");
  const users = await response.json();
  return users;
});
Enter fullscreen mode Exit fullscreen mode

然后在我们的切片中,我们将添加一个名为的属性,extraReducers该属性包含几个函数来处理 API 的返回:

  • 待办的
  • 已实现
  • 拒绝

我们的 API 调用返回一个Promise对象,它代表异步操作(在本例中为 API 调用)的状态。我们将根据 Promise 状态更新状态。

usersSlicejs

const usersSlice = createSlice({
  name: "users",
  initialState: {
    entities: [],
    loading: false,
  },
  reducers: { ... },
  extraReducers: {
    [fetchUsers.pending]: (state, action) => {
      state.loading = true;
    },
    [fetchUsers.fulfilled]: (state, action) => {
      state.loading = false;
      state.entities = [...state.entities, ...action.payload];
    },
    [fetchUsers.rejected]: (state, action) => {
      state.loading = false;
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

我们希望在应用程序加载时以及每次用户单击“加载用户”按钮时获取此用户数组。

为了在我们的应用程序加载时立即加载它,让我们在 index.js 中的组件之前调度它:

store.dispatch(fetchUsers())

并将其发送到我们的按钮上:

onClick={() => dispatch(fetchUsers())}

就这样!我们使用 React、Redux 和 Hooks 完成了 CRUD 应用的构建。

你可以在这里找到完整的源代码。
你也可以在这里查看最终的应用程序。

感谢您关注本教程,请关注我获取更多信息!😀

文章来源:https://dev.to/sanderdebr/react-redux-crud-app-for-beginners-with-hooks-2hja
PREV
使用骨架加载加速您的用户体验☠️CSS 添加动画在页面加载时消失
NEXT
JavaScript 中将字符串转换为数字的 7 种方法