Skip to main content

Лучшие практики

Atomic design-концепция:

  • Все файлы проекта должны быть строго сгруппированы и разделены по директориям.
  • Что-то большое должно собираться из чего-то малого.
  • Если проект хорошо структурирован, то его просто изменять (добавлять новую страницу, логику, сделать адаптивность и т.д.)

README.md:

  • Стиль оформления кода и архитектура проекта должны быть описаны в README.md, включая базовые примеры использования различного функционала (как создавать новую страницу/компонент, как подключать стор, как создавать новый стор, как делать запрос и т.д.).
  • Примеры взаимодействия с API также должны быть описаны в README.md.
  • В идеале, если новый разработчик приходит на проект, он должен получить все необходимые сведения о проекте и его разворачивании, прочитав README.md.

CodeStyle:

  • Соблюдать единство стиля офрмления кода и архитектуры проекта.
  • Чем меньше кода - тем лучше. (Хороший код - это код, которого нет).
  • Читаемый код лучше минималистичного.
  • В проекте не должно быть большого количества абстракций, код должен быть хорошо воспринимаемым. Кол-во абстракций нужно сводить к минимуму!.
  • Стараться не мутировать данные, ухудшается читаемость кода (в ряде случаев без этого никак).
  • Статичные данные выносить в отдельные файлы.
  • В идеале, код должен быть простым и понятным (KISS-концепция). Понятные проекты хорошо масштабируются. Все гениальное - простое, сложное должно быть обосновано.
  • Повторяющиеся блоки нужно выносить в отдельные общие переиспользуемые компоненты (DRY-концепция).
  • В идеале, файл компонента должен размещаться на экране IDE без скрола.
  • "Не изобретать велосипед!" для каких-либо сложных реализаций, а можно использовать готовые библиотеки (все уже давно написано почти на все случаи жизни). С другой стороны, если какая-то библиотека используется в проекте один раз для чего-то одного, то нужно задуматься о её целесообразности.
  • TypeScript должен быть простым и понятным, он должен помогать, а не усложнять, не нужно в нем использовать какие-то сложные непонятные конструкции, типов и интерфейсов достаточно.
  • Еcли много условий, то лучше их инкапсулировать в отдельную переменную.
  • Избегать излишней вложенности. Если есть возможность что-ниб вынести в отдельный блок - то лучше выносить.
  • Использование линтеров, форматеров, тестеров и прекоммитеров (ESLint, Stylelint, prettier) должно помогать, а не мешать разработке.
  • Если рефакторинг и оптимизация кода добавляет абстракций и ухудшает читаемость, то такой рефакторинг плохой (излишняя оптимизация и оверинжениринг).
  • "Не множь сущности без нужды"
  • Чем проще код и его структура - тем проще его поддерживать и понимать.

React:

  • Целая портянка пропсов - это плохо!!! И потом такое трудно поддерживать (такие компоненты нужно декомпозировать, добавлять в директорию parts/).
  • Файлы отдельных роутов (страниц) или объемных компонентов всегда должны быть hoc-компонентами, в которые уже импортятся под-компонеты этой страницы из components/pages/pageName.
  • Не вмешивать большое количество инлайн-стилей в логику компонента, лучше использовать css-классы
  • Использовать только функциональные компоненты (не использовать классовые вообще, классы - для мазохистов).
  • Самая сложная часть React-проектов - это взаимодействие с API, асинхронность, формы и обработка данных. К этой части проекта нужно относиться более тщательно.
  • UI-элементы не должны сильно отличаться на разных страницах (цвет, отступы, размеры и т.д.)
  • Нужно стараться отделять логику от представления. Логику лучше использовать в hoc-компонетах, а дальше передавать ее под-компонентам (view-компоненты) через пропсы.
  • Стейт не должен имееть глубокой вложенности, лучше чтобы все изменяемые поля были на одном уровне
  • Вcтавку токена лучше делать через интерцептор axios, чем просто вставлять из куков или localStorage, так как могут быть проблемы
  • Не нужно разводить "целый зоопарк" npm-пакетов, наличие того или иного пакета должно быть обосновано.
  • Тот, кто знает как приложение взаимодействует с API, тот знает само приложение.
  • Дополнительные обертки компонентов при экспорте - ПЛОХО, читаемость сильно ухудшается
  • Если нужно мутировать данные, полученные от сервера, то нужно это делать еще до рендера в компоненты, например в redux-thunk, добавить на этом этапе кастомные поля, чтобы их можно было потом легко мапить в компоненте
  • Не должно быть одинаковых пробежек циклами .map() одних и тех же массивов в одном месте. Цикл - это "тяжелая операция".
  • Деструктуризация должна быть оправдана частым использованием, так как создаются отдельные переменные
  • Перед includes() нужно делать проверку на проверяемое значение
  • Если смотрим незнакомый компонент, то сначала нужно посмотреть зависимости и хуки, а потом логику

Общие рекомендации

  • Не трогайте то, что работает!
  • Чтобы решить отдельную задачу - то нужно погружаться и фокусироваться в контекст этой задачи, не нужно грузить себя излишней информацией.
  • console.log в помощь.
  • DevTools и Redux-DevTools в помощь.
  • Прежде чем подключать новый эндпоинт, есть смысл его отдельно потестировать в Swagger, так как бэкендер, а тем более тестировщик вряд ли это сделает.
  • Пишите комментарии в "сложных" местах.
  • Readme.md должен содержать всю необходимую информацию для разворачивания проекта и описание сложных частей. Описание должно быть со всеми шагами, как "для дурака". Проекты должны просто разворачиваться.

// ПЛОХО
export default connect((state: RootState) => ({
players: state.room.players,
}))(PersonsContainer);

Мапинг

Обычный мапинг

  • Все функции-обработчики нужно выносить за пределы основного return компонента, чтобы такая функция не создавалась на каждый перерендер.
  • Массивы данных также должны быть за пределами компонента, чтобы не было постоянных инициализаций.
// отдельный файл navLinks.ts
const navLinks: INavigationLink[] = [ link1, link2, ... ];
const checkForPermission = (permission?: EPermission[]):boolean => {
if (permission) {
return !!haveAccess(permission);
}

return !permission;
}

// подсвечиваем ссылку (зеленым цветом), если у пользователя есть все права (И/И) из isAllowed
// если в поле permission ничего не указано, то также подсвечиваем ссылку
const checkForAllowed = (permission?: EPermission[]):boolean => {
if (permission) {
return permission.every(permission => haveAccess(permission));
}

return true;
}

return (
{navLinks.map(navLink => {
const { path, tooltipText, className, isAllowed, permission, name } = navLink;
const isHasPermission = checkForPermission(permission);
const isHasAllowed = checkForAllowed(isAllowed);

if (isHasPermission) {
return (
<NavLinkWithHash
to={path[0]}
title={tooltipText}
data-testid={`nav-${className}`}
className={
CN(styles.item, styles[className],
{
[styles.active]: path.includes(url),
'allowed': isHasAllowed,
}
)
}
>
<span>{name}</span>
</NavLinkWithHash>
);
}
})}
)

Шорткат

При мапинге можно использовать сокращенную запись и после => в () вставлять условия и с помощью && рендерить jsx. Но у обычного мапинга лучшая читаемость.

[item1, item2, ...].map(item => (item.id > 0) && <li key={item.id}>{item.name}</li>)

Пример

{navLinks.map(navLink => ((navLink.permission && haveAccess(navLink.permission)) || !navLink.permission) &&
<NavLinkWithHash
to={navLink.path[0]}
title={navLink.tooltipText}
data-testid={`nav-${navLink.className}`}
className={
CN(styles.item, styles[navLink.className],
{
[styles.active]: navLink.path.includes(url),//showActive(),
'allowed': isAllowed(navLink.isAllowed),
}
)
}
)}

Пример цепочки шорткатов

const { hubs } = useSelector((state: TStore) => ({
hubs: state.hubs.hubs.filter(hub => {
return state.auth.hubIds?.includes(hub.id) && (!!hub.timezone && !!hub.timeFrom && !!hub.timeTo);
})
.map(({ name, id, code }) => ({
name: code ? `${name} (${code})` : name,
value: id,
})).sort((a, b) => a.name.localeCompare(b.name))
}));

Spread и мапинг

options={
[
// опция "Без Хаба"
...(selectedRegions.length === 0
? [{
value: WITHOUT_HUB.id,
name: WITHOUT_HUB.name,
}]
: []
),

// список хабов
...hubs.map(hub => ({
value: hub.id,
name: hub.code ? `${hub.name} (${hub.code})` : hub.name,
}))
].sort((a, b) => +a.value - +b.value)
}

Плохие и хорошие примеры

Требуется найти наличие доступа haveAccess() по правам (permission)

// плохо
// 1- EPermission[][] - массив массивов
// 2 !permission || permission.reduce<boolean> - лучше в отдельной переменной
// 3 (t, c) => t && !!haveAccess(c), true - нечитаемый код

const isAllowed = (permission?: EPermission[][]): boolean => !permission || permission.reduce<boolean>(
(t, c) => t && !!haveAccess(c), true
);

// хорошо
// 1 - используем одинарный массив permission
// 2 - понятное условие
// 3 - используем every вместо reduce
const checkForAllowed = (permission?: EPermission[]):boolean => {
if (permission) {
return permission.every(permission => haveAccess(permission));
}

return true;
}