Комментарии 20
Остальные советы вполне полезные, особенно про использование хуков. Хотя про оборачивание часто используемых компонентов в хуки — мне кажется немного бесполезным. Зачем хук если сам компонент может следить за своим состоянием? Зачем плодить сущности и усложнять, если можно просто использовать компонент обёртку с состоянием?
Второй совет полезен в случае, когда у функции много параметров. Например, обертка над апи-запросом, в котором вдобавок еще часть параметров необязательная.
В совете "используйте объекты вместо условных операторов"
const Component = COMPONENT_MAP[user.type]
разве не придётся обвешивать это проверками на наличие этого user.type, дефолтным значением? Тогда как switch или if/else просто по привычке заставят об этом подумать.
Вообще обычно в этом есть смысл когда возможных компонентов много или заранее они неизвестны. В противном случае проще и чище будет вообще внутри jsx описать.
Привычки бывают разные. У одних они одни, у других другие. Так что кого-то заставят, а кого-то и нет — неважно в каком стиле код писать.
В случае Typescript/Flow вы не сможете забыть про это. В случае JS у вас оно точно так же упадёт в runtime. React не даст вам отрендерить в качестве компоненты undefined
. Т.е. ручная проверка в духе:
or else throw new Error('Unknown user type: ${user.type}`);
будет иметь плюс только в большей понятности самой ошибки.
Мне кажется, всё-таки тут разработчик этого компонента должен предусмотреть, что делать, если данные пришли "странные".
Я бы вывел ошибку, но сбросил тип на дефолтный, чтобы всё приложение из-за этого не падало… интересно, насколько такой подход сейчас считается правильным?
если данные пришли "странные"
Хех. Так?
const sum = (a: number, b: number): number => {
if (typeof a !== 'number' || !isFinite(a)) {
throw new Error(`wrong input: ${a}`);
}
if (typeof b !== 'number' || !isFinite(b)) {
throw new Error(`wrong input: ${b}`);
}
return a + b;
}
Я правильно понял вашу мысль? :)
Я бы вывел ошибку, но сбросил тип на дефолтный, чтобы всё приложение из-за этого не падало…
Такой подход имеет место быть в некоторых исключительных случаях. Нужно очень хорошо понимать последствия и критичность каждого отдельно взятого места где вы просто проглатываете ошибки, попутно отправляя их в какой-нибудь багтрекер. И точно знать что их потом там кто-нибудь читает и реагирует.
Наиболее корректный путь для большинства приложений — падать в случае ошибки. Совершенно не обязательно целиком. Пусть это будет форма, отдельно взятая панелька, может быть модуль, может даже какая-нибудь хитрая система обработки ошибок. Но не молча игнорировать её, надеясь на авось. Особенно если это хоть как-то касается финансов вашего пользователя.
Если же надёжность для вас серьёзный приоритет:
- Берём язык со статической системой типов (e.g. Typescript)
- Выкручиваем его на самый строгий режим (strict: true & type linters для eslint)
- При необходимости пишем свои правила для линтера
- Весь внешний input (скажем ответы сервера и сторонних сервисов) проводим через что-нибудь вроде io-ts
- Тесты и QA ;)
Использование этой простой стратегии делает компоненты более декларативными и упрощает понимание их кода.
Серьезно? Что же, давайте более подробно проанализируем этот кусок кода
import React from 'react'
const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>
const COMPONENT_MAP = {
student: Student,
teacher: Teacher,
guardian: Guardian
}
export default function SampleComponent({ user }) {
const Component = COMPONENT_MAP[user.type]
return (
<div>
<Component name={user.name} />
</div>
)
}
Сколько раз ревьюеру (который видит этот кусок кода первый раз) нужно будет промотать глазами чтобы понять что происходит в коде?
1) видим строчку COMPONENT_MAP[user.type]
— ага, нужно найти взглядом эту переменную из внешнего скоупа и понять что там хранится,
2) видим строчку рендера какого-то компонента <Component name={user.name} />
— опять нужно найти взглядом переменную из внешнего скоупа и понять что записано в переменную Component.
3-6) и наконец по значениям-ссылкам на три компонента которые были записаны в COMPONENT_MAP мы теперь должны найти каждый компонент и понять что он рендерит
А теперь сравним это с такой версией
import React from 'react'
export default function SampleComponent({ user }) {
return (
<div>
{user.type == "student" &&
<p>Student name: {user.name}</p>
}
{user.type == "teacher" &&
<p>Teacher name: {user.name}</p>
}
{user.type == "guardian" &&
<p>Guardian name: {user.name}</p>
}
</div>
)
}
Сколько раз в этой версии разработчику нужно будет мотать глазами чтобы понять что происходит в коде? Не проще ли становится код когда вообще не нужно мотать глазами? Не является ли вторая версия более "декларативной" ?
Серьезно?
Да
А теперь сравним это с такой версией
У вас просто использование компонента сводится к:
<p>Guardian name: {user.name}</p>
Более реалистичный пример будет включать в себя 5-7 props. И тогда вы получаете либо груду copy-paste, либо код где props высчитываются в одном месте, а используются в другом через spread operator, что тоже не добавляет читаемости.
Использование hashMap + key настолько избитый и широкоиспользуемый подходы, что я сильно удивлён что есть люди, которых это удивляет.
Вот из недавнего в моей практике. В приложении есть около 15 видов уведомлений. Я подключаю нужный компонент по типу самого уведомления. Все компоненты имеют единый API. Я не пишу 15 IF-ов. Я пишу hashmap (типизированный с TS).
const notificationsMap: Record<NotificationType, React.ComponentType<{ notification: Notification }> = { ... };
const NotificationItem = ({ notification }: { notification: Notification }) => <notificationsMap[notification.type] {...{ notification }}/>;
код получается примерно таким. И да, разумеется, чтобы "понять всё" придётся побегать по файлам. Ну а как иначе то? Сделав тут портянку IF-ов проблема никуда не исчезнет.
В случае TS это особенно удобно ввиду того что без лишних телодвижений вы автоматически получаете exhaustive проверку.
За совет #3 нужно увольнять.
Речь идёт о том, что следует избегать использования оператора spread ({...props}) при передаче свойств от родительских компонентов дочерним.
Я бы не был столь категоричен. Есть случаи, когда иначе никак: обёртки (wrappers).
Простые советы по написанию чистого кода React-компонентов