reactjs - Redux 如何在嵌套属性上使用 createEntityAdapter?
问题描述
我正在使用 React/Redux 创建一个应用程序,并且我不熟悉使用 React 和 Redux。我一直在试图弄清楚如何构建 redux 商店。这是我要实现的目标的简要描述,使用书籍示例进行概括,如 redux createEntityAdapter 文档
我有一个包含书籍列表的页面。用户可以点击一本书或多本书,并为每本书显示一个章节列表。可以随时添加新的或缺少的章节,因为它们在服务器上可用。添加的章节需要添加到列表中的正确位置。
- 第一册
- 第1章
- 第2章
- 第3章
- 第 6 章
- 书 2
- 书 3
书单是一个组件,章节列表是另一个组件,但是由于书籍很多,所以章节列表组件的实例也会很多。每个组件都有自己的切片。
店铺长这样:
bookList : [ Book1, Book2, Book3, ...]
ChapterList: {
Book1 : {
chapters: [
{
Chapter : 'Chapter1',
info: 'Chapter info',
other: 'more stuff'
},
{
Chapter : 'Chapter2',
info: 'Chapter info',
other: 'more stuff'
},
{...}
],
bookInfo: 'info about book'
},
Book2 : { ... },
Book3 : { ... }
}
问题是章节信息正在流式传输,因此会不断更新,丢失的章节可能随时到达。因此,每次新数据到达时,我都需要对章节数组进行排序,而数组不是很长,大约 50 个元素,新数据每秒到达几次。
我打算使用 createEntityAdapter 来规范化 chapters 数组,但鉴于它嵌套在 Book# 属性中并且需要 Book# 属性,因为我正在使用 ChapterList 组件的多个实例,所以我看不出这是怎么可能的。
const ChapterListAdapter = createEntityAdapter({
// where bookNum needs to Book1, Book2, Book3 etc...
selectId: (chapterList) => chapterList[bookNum].chapters,
sortComparer: (a, b) => a.chapters.localeCompare(b.chapters
})
我该如何解决这个问题?修改店铺?有没有办法展平章节列表,使章节处于顶层?我是否将自己的规范化/排序逻辑写入减速器?
解决方案
有没有办法展平章节列表,使章节处于顶层?我是否将自己的规范化/排序逻辑写入减速器?
您可以查看有关标准化状态形状的 Redux 指南作为起点。
您在这里有两个实体:“书籍”和“章节”。它们之间存在一种关系:每一章都属于一本书,每本书都包含一个有序的章节列表。您需要两个实体适配器。你可以用一两片——两片可能更容易,但没关系。
您标准化的状态形状应如下所示:
{
books: {
ids: [1, 2, 3],
entities: {
1: {
id: 1,
title: "Book 1",
bookInfo: 'info about book',
chapterIds: [75, 962, 64], // an ordered array of chapters
},
2: { /* ... */ },
3: { /* ... */ }
}
},
chapters: {
ids: [64, 75, 962],
entities: {
64: {
id: 64,
name: "Some Chapter Title",
info: "",
other: "",
bookId: 1 // the id of the book that this chapter belongs to
},
75: { /* ... */ },
962: { /* ... */ }
}
}
}
在 TypeScript 术语中:
import { EntityState, EntityId } from "@reduxjs/toolkit";
export interface Book {
id: EntityId;
title: string;
bookInfo: string;
chapterIds: EntityId[];
}
export interface Chapter {
id: EntityId;
name: string;
info: string,
other: string;
bookId: EntityId;
}
interface State {
books: EntityState<Book>;
chapters: EntityState<Chapter>;
}
我很难在不知道数据来自服务器时的形状的情况下编写您的减速器。根据你所说的关于一章一章的内容,听起来它已经正常化了?
编写组件对我来说更容易。我们有选择器从 id 中选择完整的实体对象:
const bookAdapter = createEntityAdapter<Book>();
const chapterAdapter = createEntityAdapter<Chapter>();
export const {
selectIds: selectBookIds,
selectById: selectBookById
} = bookAdapter.getSelectors((state: State) => state.books);
export const {
selectById: selectChapterById
} = chapterAdapter.getSelectors((state: State) => state.chapters);
所以每个组件需要的唯一道具是id
:
const Chapter = ({ id }: { id: EntityId }) => {
const chapter = useSelector((state) => selectChapterById(state, id));
// probably some hook here to request the chapter if not loaded
// dispatch an async thunk inside of a useEffect
if (!chapter) {
return <Loading />;
}
return (
<div>
<h3>{chapter.name}</h3>
<p>{chapter.info}</p>
</div>
);
};
const Book = ({ id }: { id: EntityId }) => {
const book = useSelector((state) => selectBookById(state, id));
// probably some hook here to request the book if not loaded
// dispatch an async thunk inside of a useEffect
if (!book) {
return <Loading />;
}
return (
<div>
<h1>{book.title}</h1>
<p>{book.bookInfo}</p>
<h2>Chapters</h2>
<ul>
{book.chapterIds.map((chapterId) => (
<li key={chapterId}>
<Chapter id={chapterId} />
</li>
))}
</ul>
</div>
);
};
您可以查看此答案以获取有关如何仅在尚未加载实体的情况下从 API 请求实体的更多信息。
推荐阅读
- c# - 在 Azure 和 WebApi C# .Net Core 2.2 上托管的 React 应用程序上的 Sharepoint online/365 集成(上传文件)
- javascript - 启用“同步”时,隐藏/显示数据集也会同步?
- java - 如何通过 API 设置 application.properties 运行时?
- api - 我需要帮助查找 Paypal ClientID 和 ClientSecret ID
- vb.net - 在 Mysettingscolor 中保存颜色并返回
- javascript - React Native Stacknavigation 默认导航选项上的自定义后退按钮
- visual-c++ - 使用自定义任务覆盖项目属性
- c# - NSubstitute 方法使用特定的 args 序列多次调用?
- javascript - 为什么类实例方法在调用后导致未定义。超时功能是否正确?
- sql-server - 如何在sql中通过一对获取最大事务数