Redxu(RTK) 基础 异步逻辑与数据请求 第5.3节 加载用户数据

对应文档这个位置

加载用户数据

现在页面上关于作者的信息是 unknown,因为我们没有正确的拉取user信息吖。

怎么做捏?太简单了,前面已经拉取了posts,现在拉取user就行了!干就完了,加油奥利给。
在userSlice里写thunk。

//这是注释,显示文件路径捏:/src/features/users/usersSlice.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
import { createSlice } from "@reduxjs/toolkit";
import { client } from "../../api/client";

interface IState {
  users: IUser[];
  status: "idle" | "pending" | "success" | "failed";
  error: null | any;
}
type IUser = {
  id: string;
  name: string;
};
const initialState: IState = {
  status: "idle",
  error: null,
  users: [],
};

export const fetchUsers = createAsyncThunk("users/fecthUsers", async () => {
  const response = await client.get("/fakeApi/users");
  return response.data;
});
const usersSlice = createSlice({
  name: "users",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchUsers.fulfilled, (state, action) => {
      console.log(action.payload);

      state.users = action.payload;
    });
  },
});

export default usersSlice.reducer;

跟文档有点不同,文档在extraReducers 用了另一种写法 ,不过这没啥问题。

builder.addCase(fetchUsers.fulfilled, (state, action) => {
      return action.payload
    })
  }

然后到App.tsx里边拉取user数据,只用在app启动前拉取一次就行了。

function App() {
  const dispatch = useAppDispatch();
  dispatch(fetchUsers());
// 省略其他内容捏!

现在页面上的作者应该已经能够正常显示了捏!

添加新帖子

到第五节的最后一个小节了。
当我们点击新增post的时候,数据还是存在内存里,因为有模拟的后端了,我们要进行API调用,把数据存到数据库里,当然要注意,因为是mock的后端,所以刷新页面后新增数据不会持久化,但是我们的核心在用mock后端进行异步的post请求,下面进行具体操作捏!

使用thunk发送新帖子的数据

首先还是写个thunk,如下:
注意 initialPost参数实际上就是从客户端接收到的表单数据,post到服务器后,后端要返回带有id等其他数据的完整的post数据。

export const addNewPost = createAsyncThunk(
  "posts/addNewPost",
  // payload 创建者接收部分“{title, content, user}”对象
  async (initialPost: Pick<IPost, "title" | "user" | "content">) => {
    // 我们发送初始数据到 API server
    const response = await client.post("/fakeApi/posts", initialPost);
    // 响应包括完整的帖子对象,包括唯一 ID
    return response.data;
  }
);

检查组件中的 Thunk 结果

在等待请求时可以禁用 “保存帖子” 按钮,那么用户就不会意外地尝试保存帖子两次,这样交互会更友好。如果请求失败,我们可能还想在表单中显示错误消息,或者只是将其记录到控制台。
可以让我们的组件逻辑等待异步 thunk 完成,并在完成后检查结果:

上面内容意思就是,我们要跟踪thunk的状态,只在thunk真正fulfilled的时候才能更新数据捏。
额,下面有点多,其实就是更新了addPost这个thunk的使用,另外根据thunk实际请求状态来判断是否请求成功,如果还在请求,那么保存按钮是不可用的。

//这是注释,显示文件路径捏:/src/features/posts/AddPostForm.tsx

import React, { useState } from "react";
import { nanoid } from "@reduxjs/toolkit";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { addNewPost, postAdd } from "./postsSlice";
export const AddPostForm = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const dispatch = useAppDispatch();
  const [userId, setUserId] = useState("");
  const [addRequestStatus, setAddRequestStatus] = useState("idle");

  const users = useAppSelector((state) => state.users);

  const onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
    setTitle(e.target.value);
  const onContentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
    setContent(e.target.value);
  const onAuthorChanged = (e: React.ChangeEvent<HTMLSelectElement>) =>
    setUserId(e.target.value);
  const canSave =
    [title, content, userId].every(Boolean) && addRequestStatus === "idle";

  const onSavePostClicked = async () => {
    if (canSave) {
      try {
        setAddRequestStatus("pending");
        await dispatch(addNewPost({ title, content, user: userId })).unwrap();
        setTitle("");
        setContent("");
        setUserId("");
      } catch (err) {
        console.error("Failed to save the post: ", err);
      } finally {
        setAddRequestStatus("idle");
      }
    }
  };
  // 这是个 option 组件啊,造出来一个下拉菜单的效果
  const usersOptions = users.users.map((user) => (
    <option key={user.id} value={user.id}>
      {user.name}
    </option>
  ));
  return (
    <section>
      <h2>添加新文章</h2>
      <form>
        <label htmlFor="postTitle">文章标题:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          value={title}
          onChange={onTitleChanged}
        />
        <label htmlFor="postAuthor">Author:</label>
        <select id="postAuthor" value={userId} onChange={onAuthorChanged}>
          <option value=""></option>
          {usersOptions}
        </select>
        <label htmlFor="postContent">内容:</label>
        <textarea
          id="postContent"
          name="postContent"
          value={content}
          onChange={onContentChanged}
        />

        <button type="button" onClick={onSavePostClicked} disabled={!canSave}>
          保存文章
        </button>
      </form>
    </section>
  );
};

当然,这个addNewPost对应的extraReducers我没写,你可以自己试一试。

可以使用 useState 添加一个表示加载状态的枚举状态,类似于我们在 postsSlice 中跟踪加载状态以获取帖子的方式。这样的话,就能知道请求是否在进行中。
当我们调用 dispatch(addNewPost()) 时,异步 thunk 从 dispatch 返回一个 Promise。我们可以在这里 await 那个 promise 知道 thunk 什么时候完成它的请求。但是,我们还不知道该请求是成功还是失败。
createAsyncThunk 在内部处理了所有错误,因此我们在日志中看不到任何关于“rejected Promises”的消息。然后它返回它 dispatch 的最终 action:如果成功则返回“已完成” action,如果失败则返回“拒绝” action。Redux Toolkit 有一个名为 unwrapResult 的工具函数,它将返回来自 fulfilled action 的 action.payload 数据,或者如果它是 rejected action 则抛出错误。这让我们可以使用正常的 try/catch 逻辑处理组件中的成功和失败。因此,如果帖子成功创建,我们将清除输入字段以重置表单,如果失败,则将错误记录到控制台。
但是,想要编写查看实际请求成功或失败的逻辑是很常见的。 Redux Toolkit 向返回的 Promise 添加了一个 .unwrap() 函数,它将返回一个新的 Promise,这个 Promise 在 fulfilled 状态时返回实际的 action.payload 值,或者在 “rejected” 状态下抛出错误。这让我们可以使用正常的“try/catch”逻辑处理组件中的成功和失败。 因此,如果帖子创建成功,我们将清除输入字段以重置表单,如果失败,则将错误记录到控制台。
如果你想查看 addNewPost API 调用失败时如何处理,请尝试创建一个新帖子,其中“内容”字段只有“error”一词(不含引号)。服务器将看到并发送回失败的响应,这样你应该会看到控制台记录一条日志。

总结
可以编写可复用的“selector 选择器”函数来封装从 Redux 状态中读取数据的逻辑
选择器是一种函数,它接收 Redux state 作为参数,并返回一些数据
Redux 使用叫做“ middleware ”这样的插件模式来开发异步逻辑
官方的处理异步 middleware 叫 redux-thunk,包含在 Redux Toolkit 中
Thunk 函数接收 dispatch 和getState 作为参数,并且可以在异步逻辑中使用它们
你可以 dispatch 其他 action 来帮助跟踪 API 调用的加载状态
典型的模式是在调用之前 dispatch 一个 "pending" 的 action,然后是包含数据的 “sucdess” 或包含错误的 “failure” action
加载状态通常应该使用枚举类型,如 'idle' | 'loading' | 'succeeded' | 'failed'
Redux Toolkit 有一个 createAsyncThunk API 可以为你 dispatch 这些 action
createAsyncThunk 接受一个 “payload creator” 回调函数,它应该返回一个 Promise,并自动生成 pending/fulfilled/rejected action 类型
像 fetchPosts 这样生成的 action creator 根据你返回的 Promise dispatch 这些 action
可以使用 extraReducers 字段在 createSlice 中监听这些 action,并根据这些 action 更新 reducer 中的状态。
action creator 可用于自动填充 extraReducers 对象的键,以便切片知道要监听的 action。
Thunk 可以返回 promise。 具体对于createAsyncThunk,你可以await dispatch(someThunk()).unwrap()来处理组件级别的请求成功或失败。

这是本章示例对应的codepen

posted @ 2023-03-13 16:06  刘老六  阅读(93)  评论(0编辑  收藏  举报