首页 > 其他分享 >Redxu(RTK) 基础 性能与数据范式化 6.2 添加通知

Redxu(RTK) 基础 性能与数据范式化 6.2 添加通知

时间:2023-03-16 21:23:10浏览次数:70  
标签:const .. 范式化 notification RTK notifications state 6.2 import

添加通知

如果不能弹出通知告诉我们有人发布了消息、留下了评论或者对我们的帖子做出了回应,那么任何社交媒体应用都是不完整的。
在一个真实的应用中,我们的客户端和后端服务器进行持续的通信,当发生某些事情时,服务器都会向客户端推送通知。由于我们这是一个小型应用,我们将通过添加一个按钮从虚假的 API 中获取一些通知来模拟该过程。我们也没有任何用来发送消息或对帖子做出反应的真实用户,因此虚假 API 会在我们每次发出请求时创建一些随机通知条目。(请记住,这里的目标是了解如何使用 Redux 本身。)

文档项目要求我们开始一个新功能,就是现在常见的新动态推送功能,这个功能依然用到了server.js提供的服务。

通知 slice

由于这是一个船新的功能,那么我们在feature下组织一个新的slice来管理其数据,在其中创建thunk用于向服务器发起拉取新动态。另外,为了创建一些真实的通知,我们将在 state 中的保存最新通知的时间戳。这将使模拟服务器生成比该时间戳更新的通知:

//这是注释,显示文件路径捏:/src/features/notifications/notificationSlice.tsx
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

import { client } from "../../api/client";
import { RootState } from "../../app/store";

export const fetchNotifications = createAsyncThunk<
  // Return type of the payload creator
  any,
  // First argument to the payload creator
  undefined,
  // Optional fields for defining thunkApi field types
  { state: RootState }
>("notifications/fetchNotifications", async (_, { getState }) => {
  const allNotifications = selectAllNotifications(getState());
  const [latestNotification] = allNotifications;
  const latestTimestamp = latestNotification ? latestNotification.date : "";
  const response = await client.get(
    `/fakeApi/notifications?since=${latestTimestamp}`
  );
  return response.data;
});
//这里我还不知道notification的具体类型,先放个空的在这占位置
interface IState {}
let initialState: any[] = [];
const notificationsSlice = createSlice({
  name: "notifications",
  initialState: initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(fetchNotifications.fulfilled, (state, action) => {
      state.push(...action.payload);
      // 以最新的优先排序
      state.sort((a, b) => b.date.localeCompare(a.date));
    });
  },
});

export default notificationsSlice.reducer;

export const selectAllNotifications = (state: RootState) => state.notifications;


If you need to modify the types of the thunkApi parameter, such as supplying the type of the state returned by getState(), you must supply the first two generic arguments for return type and payload argument, plus whicher "thunkApi argument fields" are relevant in an object:

关于为什么给creatAsyncThunk提供泛型标注,请看这里Typing createAsyncThunk
和其他的 slice 一样,将 notificationsReducer 引入 store.js, 并将其添加到 configureStore() 的调用中。

fetchNotifications 能帮助我们从服务端拉取最新的通知。从服务端返回的数据被放入notifications这个slice,并且总是以新的在前旧的在后的顺序排列。

Thunk参数

我们之前说过,thunk可以被直接dispatch,这里文档用了个挺形象的说法,把dispatch一个thunk的时候dispatch内部的thunk叫做 thunk action。
dispatch(addPost(newPost))比如说这里的addPost是个thunk,在他被dispatch的时候,把他形象地说成是一个thunk action。
这个thunk的action会是createAsyncThunk的第二个参数(payload creator)的第一个参数,有点绕哈。

export const incrementAsync = createAsyncThunk(
  "counter/fetchCount",
// 下面这个函数就是payload creator,因为他的返回值就会被包装成一个action的payload捏!
  async (amount: number, thunkAPI) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

我们之前稍微讲过createAsyncThunk的payload creator第二个参数(是个函数),它就是一个'thunk API'对象,上面有必要的函数/信息什么的:

  • dispatch 和 getState:dispatch 和 getState 方法由 Redux store 提供。你可以在 thunk 中使用这些来发起 action,或者从最新的 Redux store 中获取 state (例如在发起 另一个 action 后获取更新后的值)。
  • extra:当创建 store 时,用于传递给 thunk 中间件的“额外参数”。这通常时某种 API 的包装器,比如一组知道如何对应用程序的服务器进行 API 调用并返回数据的函数,这样你的 thunk 就不必直接包含所有的 URL 和查询逻辑。
  • requestId:该 thunk 调用的唯一随机 ID ,用于跟踪单个请求的状态。
  • signal:一个AbortController.signal 函数,可用于取消正在进行的请求。
  • rejectWithValue:一个用于当 thunk 收到一个错误时帮助自定义 rejected action 内容的工具。

当然这是createAsyncThunk api提供的,你要头铁手写thunk,那形式是thunk 函数将获取 (dispatch, getState)作为单独的参数。
这里用到thunkAPI就是因为notifications存在store里,所以从thunkAPI中结构到getState,读取对应数据。

添加通知列表

创建slice之后,我们可以添加一个 组件:当然是用于显示notifications了。

//这是注释,显示文件路径捏:/src/features/notifications/NotificationList.tsx
import React from "react";
import { useSelector } from "react-redux";
import { formatDistanceToNow, parseISO } from "date-fns";

import { selectAllUsers } from "../users/usersSlice";

import { selectAllNotifications } from "./notificationSlice";

export const NotificationsList = () => {
  const notifications = useSelector(selectAllNotifications);
  const users = useSelector(selectAllUsers);

  const renderedNotifications = notifications.map((notification) => {
    const date = parseISO(notification.date);
    const timeAgo = formatDistanceToNow(date);
    const user = users.find((user) => user.id === notification.user) || {
      name: "Unknown User",
    };

    return (
      <div key={notification.id} className="notification">
        <div>
          <b>{user.name}</b> {notification.message}
        </div>
        <div title={notification.date}>
          <i>{timeAgo} ago</i>
        </div>
      </div>
    );
  });

  return (
    <section className="notificationsList">
      <h2>Notifications</h2>
      {renderedNotifications}
    </section>
  );
};

这是个UI组件,就是显示从后端拉取的notification。
继续,在添加一个“通知”tab,以及用于获取“新的”通知的按钮

//这是注释,显示文件路径捏:/src/components/Navbar.tsx
import React from "react";

import { Link } from "react-router-dom";
import { useAppDispatch } from "../app/hooks";
import { fetchNotifications } from "../features/notifications/notificationSlice";

export const Navbar = () => {
  const dispatch = useAppDispatch();

  const fetchNewNotifications = () => {
    dispatch(fetchNotifications());
  };
  return (
    <nav>
      <section>
        <h1>Redux 基础教程示例</h1>

        <div className="navContent">
          <div className="navLinks">
            <Link to="/">文章列表</Link>
            <Link to="/users">用户列表</Link>
            <Link to="/notifications">动态</Link>
          </div>
          <button className="button" onClick={fetchNewNotifications}>
            刷了个新 动态
          </button>
        </div>
      </section>
    </nav>
  );
};

最后肯定还是给显示notification的页面配置路由捏,这个咱就不详细写了,就写配置路由的一行就可以了(注意我的和文档有点区别,还是因为react-router版本不同捏!)

 <Route
            path="/notifications"
            element={<NotificationsList></NotificationsList>}
          ></Route>

然后页面大概这样:

显示新通知

文档示例给的这个后端服务,挺酷的,每次我们点击刷新动态,都会返回一些新的“通知”,越刷越多,现在我们添加一些逻辑,方便查看哪些新的“通知”是已读的,哪些是未读的。

因为fakeApi提供的数据没有 用于区分到底是已读还是未读的字段(也没有对应的逻辑捏),所以我们得自己加上去额。
下面是fakeApi提供的Notification数据。

显示新通知

现在我们每次点击“刷了个新动态”都能从后端拉取新的数据,从而在页面上显示新的通知。现在我们添加一些新逻辑,
在NotificationList中首先给所有notification数据加入isRead的字段,并设置为布尔值真,
并在向后端继续发送请求拉取“新的动态”后,遍历所有notification数据,给每个数据加入isNew字段,并对已经设置isRead的数据,对应设置isNew为布尔值假。
技术逻辑上,这里用了useLayoutEffect,也就是每次拉取数据,每次重新渲染,上一次渲染的内容都会被useLayoutEffect钩子里边 dispatch的allNotificationsRead更新为isRead,那么拉取后端数据的时候,只要是isRead为真的数据,其isNew就是假,就用这个isNew来在页面上差异化显示。
不过说实话,实际情况和这个肯定不同,这段的核心在于提供一个思路,告诉读者,每次拉取后端数据的时候,可以根据客户端已有操作来遍历所有数据,标记和差异化渲染页面。

//这是注释,显示文件路径捏:/src/features/notifications/notificationSlice.tsx
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

import { client } from "../../api/client";
import { RootState } from "../../app/store";

export const fetchNotifications = createAsyncThunk<
  // Return type of the payload creator
  any,
  // First argument to the payload creator
  undefined,
  // Optional fields for defining thunkApi field types
  { state: RootState }
>("notifications/fetchNotifications", async (_, { getState }) => {
  const allNotifications = selectAllNotifications(getState());
  const [latestNotification] = allNotifications;
  const latestTimestamp = latestNotification ? latestNotification.date : "";
  const response = await client.get(
    `/fakeApi/notifications?since=${latestTimestamp}`
  );

  return response.data;
});
//这里我还不知道notification的具体类型,先放个空的在这占位置
interface IState {
  status: "idle" | "pending" | "success" | "failed";
  error: null | any;
  notifications: any[];
}
let initialState: any[] = [];
const notificationsSlice = createSlice({
  name: "notifications",
  initialState: initialState,
  reducers: {
    allNotificationsRead(state, action) {
      state.forEach((notification) => {
        notification.read = true;
      });
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchNotifications.pending, (state, action) => {})
      .addCase(fetchNotifications.fulfilled, (state, action) => {
        state.forEach((notification) => {
          // Any notifications we've read are no longer new
          notification.isNew = !notification.read;
        });
        state.push(...action.payload);
        // 以最新的优先排序
        state.sort((a, b) => b.date.localeCompare(a.date));
      });
  },
});

export default notificationsSlice.reducer;
export const {allNotificationsRead} =notificationsSlice.actions
export const selectAllNotifications = (state: RootState) => state.notifications;

下面是可以差异化渲染‘新、旧’notification的变动后的页面,注意,使用了classNames这个库。

//这是注释,显示文件路径捏:/src/features/notifications/NotificationList.tsx
import React from "react";
import { formatDistanceToNow, parseISO } from "date-fns";

import { selectAllUsers } from "../users/usersSlice";

import {
  selectAllNotifications,
  allNotificationsRead,
} from "./notificationSlice";
import classnames from "classnames";
import { useAppSelector, useAppDispatch } from "../../app/hooks";
export const NotificationsList = () => {
  const dispatch = useAppDispatch();
  const notifications = useAppSelector(selectAllNotifications);
  const users = useAppSelector(selectAllUsers);
  //在下次渲染之前 read 所有已有的notification呢!
  React.useLayoutEffect(() => {
    dispatch(allNotificationsRead());
  });

  const renderedNotifications = notifications.map((notification) => {
    const date = parseISO(notification.date);
    const timeAgo = formatDistanceToNow(date);
    const user = users.find((user) => user.id === notification.user) || {
      name: "Unknown User",
    };
    const shouldBeNew = notification.isNew ===undefined
    const notificationClassname = classnames("notification", {
      new: shouldBeNew,
    });
    return (
      <div key={notification.id} className={notificationClassname}>
        <div>
          <b>{user.name}</b> {notification.message}
        </div>
        <div title={notification.date}>
          <i>{timeAgo} ago</i>
        </div>
      </div>
    );
  });

  return (
    <section className="notificationsList">
      <h2>Notifications</h2>
      {renderedNotifications}
    </section>
  );
};

这一小节最后内容:我们需要在导航栏中的“通知” tab 上添加“徽章”。当我们在其他 tab 中时,这将向我们展示“未读”通知的条数:

//这是注释,显示文件路径捏:/src/components/Navbar.tsx
import React from "react";

import { Link } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "../app/hooks";
import {
  fetchNotifications,
  selectAllNotifications,
} from "../features/notifications/notificationSlice";

export const Navbar = () => {
  const dispatch = useAppDispatch();
  const notifications = useAppSelector(selectAllNotifications);
  const numUnreadNotifications = notifications.filter((n) => !n.read).length;

  const fetchNewNotifications = () => {
    dispatch(fetchNotifications());
  };

  let unreadNotificationsBadge;
  //只是读取notification的read属性并进行一些判断
  if (numUnreadNotifications > 0) {
    unreadNotificationsBadge = (
      <span className="badge">{numUnreadNotifications}</span>
    );
  }
  return (
    <nav>
      <section>
        <h1>Redux 基础教程示例</h1>

        <div className="navContent">
          <div className="navLinks">
            <Link to="/">文章列表</Link>
            <Link to="/users">用户列表</Link>
            <Link to="/notifications">动态 {unreadNotificationsBadge}</Link>
          </div>
          <button className="button" onClick={fetchNewNotifications}>
            刷了个新 动态
          </button>
        </div>
      </section>
    </nav>
  );
};

标签:const,..,范式化,notification,RTK,notifications,state,6.2,import
From: https://www.cnblogs.com/nulixuexipython/p/17219001.html

相关文章

  • #创作者激励#OpenHarmony富设备移植指南(6.2)GPU测试程序编译
    【本文正在参加2023年第一期优质创作者激励计划】上一篇文章讲解了编译开源gpu驱动,并把gpu驱动添加到编译框架中,此时理论上gpu已经可以调用,但是我们需要一些......
  • 黑群晖更新 6.2.4 后失联拯救
    昨天下班前看了一眼群晖提示更新,就手贱点了更新到6.2.4然后开始更新等待重启。本想着今天早上来应该会更新完成,我访问群晖的IP网页报错。进到虚拟机查看黑群的虚拟机在......
  • P1075 [NOIP2012 普及组] 质因数分解 提交 333.88k 通过 126.26k 时间限制 1.00s 内存
    P1075[NOIP2012普及组]质因数分解[NOIP2012普及组]质因数分解题目描述已知正整数n是两个不同的质数的乘积,试求出两者中较大的那个质数。输入格式输入一个正整......
  • Docker部署mysql5.7与redis6.2.6
    Linux环境:centos7.6#首先创建docker相关数据卷挂载目录mkdir-pdocker/{nexus3,mysql,redis}一、部署mysql1.搜索版本dockersearchmysql2.安装mysql5.7dockerpu......
  • Redxu(RTK) 基础 异步逻辑与数据请求 第5.2.1节 加载帖子第二部分 使用 createAsyncTh
    本篇学习如何正确编写和使用thunk,并且学习通过获取thunk状态在页面上显示不同内容(比如提示正在加载、加载失败、或者是显示加载成功后的数据)的范式。createAsyncThunk请求......
  • kubeadm 安装1.16.2版本k8s
    环境介绍 环境准备#修改hostnamehostnamectlset-hostnameyour-new-host-name#查看修改结果hostnamectlstatus#设置hostname解析echo"127.0.......
  • Linux 6.2 已正式发布
    LinusTorvalds发布了稳定的 Linux 6.2内核,这是2023年的第一个主要内核版本。硬件方面,Linux6.2提升了IntelArc显卡(DG2/Alchemist)的稳定性,真正做到......
  • Linux 6.2 已正式发布
    LinusTorvalds发布了稳定的 ​​Linux​​ 6.2内核,这是2023年的第一个主要内核版本。硬件方面,Linux6.2提升了IntelArc显卡(DG2/Alchemist)的稳定性,真正做到开......
  • Linux 6.2 已正式发布
    LinusTorvalds发布了稳定的 ​​Linux​​ 6.2内核,这是2023年的第一个主要内核版本。硬件方面,Linux6.2提升了IntelArc显卡(DG2/Alchemist)的稳定性,真正做到开......
  • 16.2 Kubernetes - Helm(Chart 结构)
    ChartHelm使用一种名为charts的包格式,一个chart是描述一组相关的Kubernetes资源的文件集合。单个chart既可以用于部署简单的应用,也可以是复杂的应用。Charts是......