【问题标题】:Where to query the state in a React app with normalized store?在具有规范化商店的 React 应用程序中在哪里查询状态?
【发布时间】:2021-11-26 14:38:14
【问题描述】:

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

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

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

好东西,效果很好。

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

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

旧方法

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 用户更改组件的 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}" />;
      })}
    </>
  );
};

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

【问题讨论】:

    标签: reactjs redux storybook


    【解决方案1】:

    最后我想到了像下面这样包装Comment 组件。似乎是一种公认​​的方法,因为它类似于 a blog post about React anti patterns 中“智能”和“演示”组件之间的区别。

    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}" />;
          })}
        </>
      );
    };
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-23
      • 2014-09-09
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 2018-07-07
      • 2013-02-12
      • 1970-01-01
      相关资源
      最近更新 更多