首页 > 其他分享 >Redxu(RTK) 基础 异步逻辑与数据请求 第5.2 节 加载帖子

Redxu(RTK) 基础 异步逻辑与数据请求 第5.2 节 加载帖子

时间:2023-03-07 15:13:29浏览次数:50  
标签:5.2 const posts RTK id state date post Redxu

加载帖子

之前我们在postSlice做了硬编码的post作为其初始状态,现在我们改一下,改成从服务器获取帖子数据。

提取帖子selectors

我们已经在slice中编写过reducer、prepare和thunk,现在继续介绍一个实践,在slice中编写 selector,这个选择器就是我们在 useSelector钩子里传入的函数,
比如下面图片中的函数,就是SinglePostPage.tsx中的用于选取具体数据的函数,为了避免在多个UI组件中重复编写这类代码,文档建议我们把处理同一“块儿”数据的这类函数统一组织在slice文件中集中存放,方便后序管理和引入调用(虽然这不是必须的捏,但这其实更得益于RTK更新后两个钩子的出现,以前的redux中,selector的手动编写也是很麻烦的)。

下面我依照文档把 PostsList.js SinglePostPage.js EditPostForm.js三个文件中的selector函数提取到slice中,并对应使用修改后的函数

//这是注释,显示文件路径捏:/src/features/posts/PostList.tsx
  // 修改前
  // const posts = useAppSelector((state) => state.posts);
  // 修改后
  const posts = useAppSelector(selectAllPosts);
//这是注释,显示文件路径捏:/src/features/posts/SinglePostPage.tsx
   //修改前
  // const post = useAppSelector((state) =>
  //   state.posts.find((post) => post.id === postId)
  // );
  // 修改后
  const post = useAppSelector((state) => selectPostById(state, postId));
  //修改前
  // let post = initializeUndefinedPostWhenErrorHappensHelper(
  //   useAppSelector((state) => state.posts.find((post) => post.id === postId))
  // );
  // 修改后
  let post = initializeUndefinedPostWhenErrorHappensHelper(
    useAppSelector((state) => selectPostById(state,postId))
  );

相对来说可以简化一些,尤其在需要编写的功能增多时,重复逻辑可能会增加,这个时候抽取selector是事半功倍的!
但是也可以不这么做,自由度很高哒。

通过编写可重用的选择器来封装数据查找通常是一个好主意。你还可以创建有助于提高性能的“记忆化 memoized” 选择器,我们将在本教程的后面部分进行介绍。
但是,就像任何抽象一样,这不是你 所有 时间、任何地方都应该做的事情。编写选择器意味着需要理解和维护更多的代码。不要觉得你需要为状态的每个字段都编写选择器。建议开始时不使用任何选择器,稍后当你发现自己在应用程序代码的许多部分中查找相同值时添加一些选择器。

请求过程中的加载状态

当我们进行 API 请求时,我们可以将其进度视为一个小型状态机,它处于下面四种可能的状态之一:

  • 请求尚未开始
  • 请求正在进行中
  • 请求成功,我们现在有了我们需要的数据
  • 请求失败,可能有错误信息

我们 可以 使用一些布尔值来跟踪该信息,例如 isLoading: true,但最好将这些状态作为单个枚举值。好的模式是使用如下所示的状态部分:

{
  // 多个可能的状态枚举值
  status: 'idle' | 'loading' | 'succeeded' | 'failed',
  error: string | null
}

现在,postsSlice 状态是一个单一的 posts 数组。我们需要将其更改为具有 posts 数组以及加载状态字段的对象。
这个加载状态可以帮助我们确定异步状态当前具体处于哪个阶段(pending?succeeded?failed?)

//旧的posts initialState
 [
  {
    id: "1",
    title: "First Post!",
    content: "Hello!",
    user: "0",
    //意思就是这篇文章的创建时间是渲染时间10分钟之前(强制写的,为了示范)
    date: sub(new Date(), { minutes: 10 }).toISOString(),
    reactions: {
      thumbsUp: 0,
      hooray: 0,
      heart: 0,
      rocket: 0,
      eyes: 0,
    },
  },
  {
    id: "2",
    title: "Second Post",
    content: "More text",
    user: "1",
    date: sub(new Date(), { minutes: 5 }).toISOString(),
    reactions: {
      thumbsUp: 0,
      hooray: 0,
      heart: 0,
      rocket: 0,
      eyes: 0,
    },
  },

];

友情提示,别忘记更改对应的selector呢!

是的,这 确实 意味着我们现在有一个看起来像 state.posts.posts 的嵌套对象路径,这有点重复和愚蠢:) , 如果我们想避免这种情况, 可以 将嵌套数组名称更改为 items 或 data 或其他东西,但我们暂时保持原样。

// 更新后的 postSlice
//这是注释,显示文件路径捏:/src/features/posts/postsSlice.ts
import { RootState } from "./../../app/store";
import { createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit";
import { sub } from "date-fns";
const postsInitialState = [
  {
    id: "1",
    title: "First Post!",
    content: "Hello!",
    user: "0",
    //意思就是这篇文章的创建时间是渲染时间10分钟之前(强制写的,为了示范)
    date: sub(new Date(), { minutes: 10 }).toISOString(),
    reactions: {
      thumbsUp: 0,
      hooray: 0,
      heart: 0,
      rocket: 0,
      eyes: 0,
    },
  },
  {
    id: "2",
    title: "Second Post",
    content: "More text",
    user: "1",
    date: sub(new Date(), { minutes: 5 }).toISOString(),
    reactions: {
      thumbsUp: 0,
      hooray: 0,
      heart: 0,
      rocket: 0,
      eyes: 0,
    },
  },
];
const initialState = {
  posts: postsInitialState,
  status: "idle",
  error: null,
};
export type Treactions = typeof initialState["posts"][number]["reactions"];
export type IPost = {
  id: string;
  content: string;
  title: string;
  //类型见usersSlice里边的内容捏
  user: string;
  //注意这里也新增了date,我试验了一下,他对ui中dispatch action没任何影响,
  //但它可以直接约束reducer的prepare中payload的构建
  //我想了一下,prepare的形参就是dispatch action时候,actionCreator的形参
  // 但是prepare内部预构建了id/date,所以这样看逻辑也很通,但是具体Typescript怎么做的,我实在不知道。
  date: string;
  reactions: Treactions;
};

const postsSlice = createSlice({
  name: "posts",
  initialState: initialState,
  //记住吖,处理数据就是reducer的责任捏!
  //同步的数据处理就全在这里呢!
  reducers: {
    postAdd: {
      reducer: (state, action: PayloadAction<IPost>) => {
        state.posts.push(action.payload);
      },
      prepare: (title, content, userId) => {
        return {
          payload: {
            id: nanoid(),
            date: new Date().toISOString(),
            title,
            content,
            user: userId,
            reactions: {
              thumbsUp: 0,
              hooray: 0,
              heart: 0,
              rocket: 0,
              eyes: 0,
            },
          },
        };
      },
    },
    postUpdated: (state, action) => {
      const { id, title, content } = action.payload;
      const existingPost = state.posts.find((post) => post.id === id);
      if (existingPost) {
        existingPost.title = title;
        existingPost.content = content;
      } else {
        // 好歹报个错吧
        console.log("花Q,你在干啥?");
      }
    },

    reactionAdded(state, action) {
      const { postId, reaction } = action.payload;
      const existingPost = state.posts.find((post) => post.id === postId);
      if (existingPost) {
        // https://stackoverflow.com/questions/57086672/element-implicitly-has-an-any-type-because-expression-of-type-string-cant-b
        existingPost.reactions[reaction as keyof Treactions]++;
      }
    },
    //end

    //如果你想给UI加上交互式的 排序。。。就得这么做,但是文档没介绍,我们先注释掉。。
    // postSort: (state) => {
    //   state.sort((a, b) => b.date.localeCompare(a.date));
    // },
  },
});

export const { postAdd, postUpdated, reactionAdded } = postsSlice.actions;

export const selectAllPosts = (state: RootState) => state.posts.posts;

export const selectPostById = (state: RootState, postId: string | undefined) =>
  state.posts.posts.find((post) => post.id === postId);
export default postsSlice.reducer;


标签:5.2,const,posts,RTK,id,state,date,post,Redxu
From: https://www.cnblogs.com/nulixuexipython/p/17165911.html

相关文章