JSX: как разделять логику и отрисовку в разметке, сгенерированной методом map
- воскресенье, 3 декабря 2023 г. в 00:00:21
Давайте представим себе частый кейс - вы с бека получаете какой-то массив данных, из которых вы будете делать разметку, что будет содержимым вашей страницы.
Для примера возьмем список пользователей, и вам надо отрендерить этот список. Вам пришел массив и вы, не теряя времени, прогоняете его через map, создавая, таким образом свою разметку - вот так:
return (
<ul className="list">
{users.map(user => (
<li>{user.name.name} {user.name.patronomic} {user.name.surname}</li>
<li>{user.regestrationDate}</li>
<li>{user.status.description}</li>
))}
</ul>
);
Окей, тут все просто. Но давайте представим, что с бека (как это часто бывает) данные вам приходят не чистыми, но их еще надо обрабатывать. Например, отчества может не быть, то есть нужно сделать проверку, а имена и фамилии нужно привести к ловеркейсу.
Также при наличии даты регистрации, привести ее к нужному виду, а если нет, то сгенерить дату и отправить ее на бек.
Ну и напоследок, если статус нужно залить соответствующим стилем из заготовленного словаря, который завязан на отдельном свойстве value; если стиля в словаре нет - сделать какой-нибудь дефолтный.
Делать все эти операции в разметке плохая идея, ведь нам хочется как раз разгрузить разметку, так что давайте сразу все вынесем в отдельные переменные.
Давайте представим как это могло бы выглядеть:
return (
<ul className="list">
{users.map(user => {
const fullName = `
{user.name.name}
{user.name.patronomic ? user.name.patronomic : ''}
{user.name.surname}
`.toLowerCase();
const date = user.regestrationDate
? moment(user.regestrationDate)
: null;
const statusColor = ['В сети', 'Не в сети'].includes(
user.status.description,
)
? STATUS_COLORS[user.status.value]
: 'neutral';
retrun (
<li>{fullName}</li>
<li>{date}</li>
<li className={statusColor}>{user.status.description}</li>
);
})}
</ul>
);
Супер - всего 25 строк кода. А теперь представьте что у вас на проекте все это происходит в компоненте, где разметка занимает несколько сот строк кода (хотя бы). И такие простые утилитарные обработки отвлекают внимание и мешают увидеть общую картину разметки, а это в больших сложных проектах критично.
Что тут можно сделать? Я предлагаю выносить все утилитарные вычисления в отдельную функцию с префиксом "with" или "proxy", которая будет как некая "прослойка" содержать код расчета переменных и передавать их обратно в колбек, который занимается разметкой.
Продемонстрируем. Создадим функцию "proxyUserVars", в которую обернем колбек разметки, перенесем в нее все переменные и передадим их обратно в колбек разметки с оригинальным обьектом user. Вот так:
const proxyUserVars = user => callback => {
const fullName = `
{user.name.name}
{user.name.patronomic ? user.name.patronomic : ''}
{user.name.surname}
`.toLowerCase();
const date = user.regestrationDate
? moment(user.regestrationDate)
: null;
const statusColor = ['В сети', 'Не в сети'].includes(
user.status.description,
)
? STATUS_COLORS[user.status.value]
: 'neutral';
return callback(user, {fullName, date, statusColor}); // vars = {fullName, date, statusColor}
}
return (
<ul className="list">
{users.map(proxyUserVars(
(user, {fullName, date, statusColor}) => (
<li>{fullName}</li>
<li>{date}</li>
<li className={statusColor}>{user.status.description}</li>
)
))}
</ul>
);
Обратите внимание как объявляется функция proxyUserVars - это множественный колбек. Чтобы не запутаться - первая переменная - это то что мы получаем вне proxyUserVars из map-а; вторая переменная - то что мы передаем в proxyUserVars, то есть наш колбек, который генерит разметку.
И того наша разметка заметно "похудела", а про proxyUserVars можно забыть - даже можно вынести ее в файл с утилитами, что еще больше разгрузит наш компонент.
Вот и все. Держите свою разметку в форме - пожалейте глаза и время коллег.
Спасибо.