Redux 如何在嵌套属性上使用 createEntityAdapter?

bli*_*ken 5 reactjs redux redux-toolkit

我正在使用 React/Redux 创建一个应用程序,并且我是使用 React 和 Redux 的新手。我一直在试图弄清楚如何构建 redux 存储。这是我想要实现的目标的简要描述,使用 redux createEntityAdapter 文档中的书籍示例进行概括

我有一个包含书籍列表的页面。用户可以单击一本或多本书籍,并且每本书都会显示一个章节列表。当新的或缺失的章节在服务器上可用时,可以随时添加它们。添加的章节需要添加到列表中的正确位置。

  • 书 1
    • 第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 : { ... }
}

Run Code Online (Sandbox Code Playgroud)

问题在于章节信息是流式传输的,因此会不断更新,并且丢失的章节可能随时到达。因此,每次新数据到达时,我都需要对章节数组进行排序,而该数组不是很长,大约有 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
}) 
Run Code Online (Sandbox Code Playgroud)

我该如何解决这个问题?修改商店?有没有办法压平chapterList,使章节位于顶层?我是否将自己的规范化/排序逻辑写入减速器中?

Lin*_*ste 7

有没有办法压平chapterList,使章节位于顶层?我是否将自己的规范化/排序逻辑写入减速器中?

您可以查看有关规范化状态形状的 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:  { /* ... */ }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

用 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>;
}
Run Code Online (Sandbox Code Playgroud)

如果不知道来自服务器的数据的形状,我很难编写您的减速器。根据你所说的逐一章节的内容,听起来已经正常化了?


对我来说编写组件更容易。我们有选择器,可以仅从 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);
Run Code Online (Sandbox Code Playgroud)

所以每个组件需要的唯一道具是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>
  );
};
Run Code Online (Sandbox Code Playgroud)

您可以查看此答案,了解有关如何仅在尚未加载实体时才从 API 请求实体的更多信息。