在React和Redux中,Map在规范化状态形状中引用的项的Id的最有效方法是什么?

utugiqy6  于 2022-11-24  发布在  React
关注(0)|答案(2)|浏览(107)

所以我刚刚开始学习redux,并在这个过程中阅读了他们关于“Normalizing State Shape”的文档。
其中一个要点是:
“对单个项目的任何引用都应通过存储项目的ID来完成。”
我已经按照他们的建议设置了我的状态,所以在下面的例子中,每个taskGroup都有一个由它们的id引用的任务选择,每个任务都有一个由它们的id引用的评论选择。

{
    "taskGroups": {
        "byId": {
            "taskGroup1": {
                "taskGroupId": "taskGroup1",
                "tasks": ["task1", "task2"]
                //etc etc
            },
            "taskGroup2": {
                "taskGroupId": "taskGroup2",
                "tasks": ["task2", "task3"]
                //etc etc
            }
            //etc etc
        },
        "allIds": ["taskGroup1", "taskGroup2", "taskGroup3"]
    },
    "tasks": {
        "byId": {
            "task1": {
                "taskId": "task1",
                "description": "......",
                "comments": ["comment1", "comment2"]
                //etc etc
            },
            "task2": {
                "taskId": "task2",
                "description": "......",
                "comments": ["comment3", "comment4", "comment5"]
                //etc etc
            }
            //etc etc
        },
        "allIds": ["task1", "task2", "task3", "task4"]
    },
    "comments": {
        "byId": {
            "comment1": {
                "id": "comment1",
                "comment": "....."
                //etc etc
            },
            "comment2": {
                "id": "comment2",
                "comment": "....."
                //etc etc
            }
            //etc etc
        },
        "allIds": ["comment1", "comment2", "comment3", "comment4", "comment5"]
    }
}

我理解了这个理论,也看到了像这样构造状态的好处。但在实践中,我很难Map对象中的引用数组,有点不知道应该在哪里做。
什么是最有效的方法,在哪里Map到实际项目的项目ID的引用?
在将引用作为属性传递给子组件之前,我应该在所有引用的父组件Map上执行此操作吗?还是应该将引用作为属性传递给子组件,然后在那里Map它们?
在切换到redux之前,我使用的是useContext,但状态是标准化的。我使用下面的函数来过滤每个taskGroup所需的任务。Thanks to ssube who posted this

export const filterObject = (objToFilter: any, valuesToFind: string) => {
    return Object.keys(objToFilter)
        .filter((key) => valuesToFind.includes(key))
        .reduce((obj, key) => {
            return {
                ...obj,
                [key]: objToFilter[key],
            };
        }, {});
};

然后像这样使用它(然后在我的Tasks组件中重复相同的逻辑来Map注解)

{
    Object.values(taskGroupsById)
        .sort((a, b) => a.sortOrder - b.sortOrder)
        .map((taskGroup) => {
            return (
                <TaskGroup
                    key={taskGroup.taskGroupId}
                    taskGroupTitle={taskGroup.taskGroupTitle}
                    tasks={filterObject(
                        tasksById,
                        taskGroupsById[`${taskGroup.taskGroupId}`].tasks
                    )}
                    handleDrawer={handleDrawer}
                    findTaskStatus={findTaskStatus}
                    findAssignedToTask={findAssignedToTask}
                />
            );
        });
}

这可以正常工作,但我不确定它是否违反直觉,因为它是在TaskGroup组件的多个示例中计算的,而不是只计算一次。
有没有更好的方法来实现这一点?我试着在我的切片中创建一个选择器来重新创建它,但似乎无法解决如何Map数组中的多个引用(而不是作为字符串只Map一个引用)。
任何帮助都将不胜感激,即使它只是一个推动正确的方向。谢谢!

0yycz8jy

0yycz8jy1#

在将引用作为属性传递给子组件之前,我应该在所有引用的父组件Map上执行此操作吗?还是应该将引用作为属性传递给子组件,然后在那里Map它们?
我推荐第二种方法。这可以潜在地最小化重新呈现,并且它符合Redux最佳实践连接更多组件以从存储中读取数据。如文档中所解释的:
希望有更多的UI组件订阅Redux存储区,并在更细粒度的级别阅读数据。这通常会带来更好的UI性能,因为当给定的状态发生变化时,需要呈现的组件会更少。
例如,不是只连接<UserList>组件并阅读整个用户数组,而是让<UserList>检索所有用户ID的列表,将列表项呈现为<UserListItem userId={userId}>,并连接<UserListItem>并从存储中提取它自己的用户条目。
这适用于React-Redux connect() API和useSelector()挂钩。
您有一个taskGroup对象,如下所示:

{
  taskGroupId: "taskGroup1",
  tasks: ["task1", "task2"]
...

因此,将任务id数组传递给<TaskGroup>组件是很简单的:

<TaskGroup
  key={taskGroup.taskGroupId}
  taskGroupTitle={taskGroup.taskGroupTitle}
  taskIds={taskGroup.tasks}
...

现在,TaskGroup有两种方法可以处理查找任务对象。
如果你不需要任何排序,那么你的TaskGroup就不需要选择任务对象,它可以简单地遍历id,并将选择推迟到SingleTask组件,就像docs中的<UserListItem userId={userId}>示例一样。
TaskGroup组件中,您将拥有如下内容:

<ul>
  {taskIds.map(taskId => (
     <SingleTask key={taskId} taskId={taskId} />
  ))}
</ul>

然后每个SingleTask负责访问它自己的数据。你可以定义一个选择器函数,尽管这个函数也足够简单,可以内联编写。
第一个
如果您需要根据单个任务的属性进行排序或过滤,则需要选择TaskGroup中的所有任务对象。创建一个选择器,该选择器接受id数组并返回对象数组。
你现有的filterObject函数不是特别有效,因为你的状态已经被很好地构造了。你不需要使用任何Array.includes()语句。你可以简单地通过它们的键来查找对象。

const selectTasksByIds = (state, taskIds) => {
  const tasksById = state.tasks.byId;
  return taskIds.map(id => tasksById[id]);
}

TaskGroup组件中,您将拥有:

const tasks = useSelector(state => selectTasksByIds(state, taskIds));
pepwfjgg

pepwfjgg2#

作为补充回答:Redux Toolkit包含一个createEntityAdapter API,它可以自动为规范化数据执行“CRUD”式更新,还可以为“按ID选择一个项”和“将整个项集作为数组选择”生成选择器:

相关问题