#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}" />;
})}
</>
);
};
Конечно, теперь я представил компонент, который нуждается в собственных тестах.