Где запросить состояние в приложении React с нормализованным магазином?

#reactjs #redux #storybook

Вопрос:

Я работаю над приложением React, использующим Redux, для управления несколькими типами сущностей в магазине Redux. Для настройки хранилища я использовал инструментарий Redux, который предлагает отличный подход для определения всех срезов для типов сущностей с использованием EntityAdapter s.

Размышляя о примере блога, у меня есть срез для сообщений, комментариев и пользователей, предлагающий соответствующие (асинхронные) действия, редукторы и селекторы.

Серверная часть предоставляет конечную точку для получения всех сообщений, комментариев и связанных пользователей за определенный период времени. Чтобы поместить данные из ответа в соответствующие срезы, я определил дополнительное действие в его собственном файле, которое я использую в файлах, определяющих срезы, для указания соответствующего редуктора.

Отличная штука, отлично работает.

Теперь возникают вопросы при реализации представления, которое отображает сообщение и его комментарии. До сих пор я пытался создавать компоненты реакции, которые только делают информацию настолько глупой (агностической), насколько это возможно. На протяжении всего прототипирования у меня были все сообщения, комментарии и пользователи в ненормализованной структуре, подобной JSON. Таким образом, я передал всю информацию для рендеринга в качестве реквизита. Это делает написание тестов и сборников рассказов довольно легким делом.

Но поскольку теперь у меня есть вся информация для отображения в моем магазине, я начал извлекать данные из магазина в этих простых компонентах 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 amp;amp; post.comments.map((comment) => {
        return <Comment username="{comment.username}" date="{comment.date}" title="{comment.title}" comment="{comment.comment}" />;
      })}
    </>
  );
};
 

New approach

 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 amp;amp; post.comments.map((commentId) => {
        return <Comment commentId="{comment.id}" />;
      })}
    </>
  );
};
 

В то время как «новый» подход позволяет обновлять только те компоненты, которые необходимо обновить, а также довольно хорошо уменьшает интерфейс компонентов, есть и недостаток: теперь необходимо создать соответствующий магазин Redux для тестов, а также для Сборника рассказов. И говоря о Сборнике рассказов: теперь невозможно позволить пользователю Сборника рассказов изменять реквизиты компонента.

К сожалению, «смешанный» подход не сработает:

Смешанный подход:

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

Теперь мне интересно, действительно ли мне нужно реализовать «новый подход», который означал бы дополнительную работу над тестами и историями и потерю возможности менять реквизит в сборнике рассказов? Или есть ли подход, который я пропустил в своем поиске о том, как прикрепить мои компоненты в магазин, но сохранить простой интерфейс компонентов?

Ответ №1:

В конце концов у меня возникла идея обернуть Comment компонент следующим образом. Кажется, это общепринятый подход, поскольку он аналогичен различию между «умными» и «презентационными» компонентами в сообщении в блоге о шаблонах реагирования.

 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 amp;amp; post.comments.map((commentId) => {
        return <CommentWrapper commentId="{comment.id}" />;
      })}
    </>
  );
};
 

Конечно, теперь я представил компонент, который нуждается в собственных тестах.