首页 > 解决方案 > 在具有规范化商店的 React 应用程序中在哪里查询状态?

问题描述

我正在开发一个使用 Redux 来管理 Redux 商店中的多个实体类型的 React 应用程序。为了配置商店,我使用了 Redux Toolkit,它提供了一种很好的方法来使用EntityAdapters 为实体类型定义所有切片。

考虑博客示例,我有一个用于帖子、评论和用户的切片,提供相应的(异步)操作、reducers 和选择器。

后端提供一个端点来获取给定时间段内的所有帖子、评论和相关用户。为了将响应中的数据放入相应的切片中,我在其自己的文件中定义了一个附加操作,我在定义切片的文件中使用它来指定相应的减速器。

好东西,很好用。

现在在实现呈现帖子及其评论的视图时会出现问题。到目前为止,我一直在尝试制作仅将信息呈现为尽可能愚蠢(不可知)的 React 组件。在整个原型设计过程中,我将所有帖子、评论和用户都放在了一个非标准化的 JSON 类结构中。因此,我传递了所有信息以渲染为道具。这使得编写测试和Storybook变得非常容易。

但是由于我现在拥有要在我的商店中呈现的所有信息,我开始使用这些简单的 React 组件从商店中检索数据useSelector

旧方法

export const Comment = ({username, date, title, comment}) => {
  return (
    <div>
      <p>{username}@{date}</p>
      <em>{title}</em>
      <p>{comment}</p>
    </div>
  );
};

// Posts were provided in a JSON structure in a not-normalized manner.
export const PostView = ({post}) => {
  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {post.comments && post.comments.map((comment) => {
        return <Comment username="{comment.username}" date="{comment.date}" title="{comment.title}" comment="{comment.comment}" />;
      })}
    </>
  );
};

新的方法

export const Comment = ({commentId}) => {
  const comment = useSelector((state) => selectComment(state, commentId));
  return (
    <div>
      <p>{comment.username}@{comment.date}</p>
      <em>{comment.title}</em>
      <p>{comment.comment}</p>
    </div>
  );
};

// Posts were provided in a JSON structure in a not-normalized manner.
export const PostView = ({postId}) => {
  const post = useSelector((state) => selectPost(state, postId));

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {post.comments && post.comments.map((commentId) => {
        return <Comment commentId="{comment.id}" />;
      })}
    </>
  );
};

虽然“新”方法只允许更新需要更新的组件,并且还很好地减少了组件接口,但也有一个缺点:现在需要模拟匹配的 Redux 存储以进行测试以及 Storybook。说到 Storybook:现在不可能让 Storybook 用户更改组件的 props。

不幸的是,“混合”方法行不通:

混合方法

export const Comment = ({username, date, title, comment}) => {
  return (
    <div>
      <p>{username}@{date}</p>
      <em>{title}</em>
      <p>{comment}</p>
    </div>
  );
};

export const PostView = ({postId}) => {
  const post = useSelector((state) => selectPost(state, postId));

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {post.comments && post.comments.map((commentId) => {
        const comment = useSelector((state) => selectComment(state, commentId));
        return <Comment username="{comment.username}" date="{comment.date}" title="{comment.title}" comment="{comment.comment}" />;
      })}
    </>
  );
};

现在我想知道我是否真的需要实施“新方法”,这意味着在测试和故事上做一些额外的工作,并失去改变故事书中道具的功能?或者我在搜索如何将我的组件附加到商店但保持简单的组件界面时错过了一种方法?

标签: reactjsreduxstorybook

解决方案


最后,我有了Comment像下面这样包装组件的想法。似乎是一种公认​​的方法,因为它类似于关于 React 反模式的博客文章中“智能”和“展示”组件之间的区别。

export const Comment = ({username, date, title, comment}) => {
  return (
    <div>
      <p>{username}@{date}</p>
      <em>{title}</em>
      <p>{comment}</p>
    </div>
  );
};

export const CommentWrapper = ({commendId}) => {
  const comment = useSelector((state) => selectComment(state, commentId));

  return <Comment username="{comment.username}" date="{comment.date}" title="{comment.title}" comment="{comment.comment}" />;
};

export const PostView = ({postId}) => {
  const post = useSelector((state) => selectPost(state, postId));

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {post.comments && post.comments.map((commentId) => {
        return <CommentWrapper commentId="{comment.id}" />;
      })}
    </>
  );
};

当然,我现在介绍了一个需要自己测试的组件。


推荐阅读