10 полезных методик во фронтенд-разработке React

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

Разбираясь с имеющимися проектами, вы внезапно обнаруживаете настоящий бардак: от временных решений хочется плакать, в коде отсутствуют стандарты и логика. Непонятно, как все это работает. Кажется, что даже малейшие изменения способны все порушить. Если вы думаете, что так бывает нечасто, то ошибаетесь.

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

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

1. Абсолютные пути вместо относительных

В заголовке нового проекта обычно прописывают относительные пути (переменная patch с символами “../../../../../.”). Это не лучший вариант для импорта файлов. Абсолютные пути предпочтительнее, так как дают полный путь к файлу. Для реализации их в проекте потребуются некоторые настройки, особенно в Webpack и TypeScript.

Настроить абсолютные пути в Webpack (create-react-app) довольно просто. Создаем файл в корневом каталоге проекта под названием “jsconfig.json” и прописываем там:

{
"CompilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}

При настройке TypeScript добавьте в файл “tsconfig.json” следующую конфигурацию:

{
"CompilerOptions": {
"baseUrl": "src",
"пути": {
"@/*": ["src /*"]
}
},
"include": ["src"]
}

Таким образом можно трансформировать подобный фрагмент кода:

import { Button } from '../../../../components/Button'
import { Icon } from '../../../../components/Icon'
import { Input } from '../../../../components/Input'

Во что-то более краткое и понятное для чтения:

import { Button } from '@/components/Button'
import { Icon } from '@/components/Icon'
import { Input } from '@/components/Input'

2. ”Export Barrel” для создания модулей

Значительно повысить читаемость и возможности редактирования представленного выше кода позволяет метод “export barrel”, известный также как “re-export” или экспорт баррелей. При этом в папке с экспортируемыми из нее модулями (компонентами) создается файл “index.js” (или “index.ts” в TypeScript).

Например, в папке “components” есть файлы: “Button.tsx”, ”Icon.tsx“ и “Input.tsx”. Чтобы использовать “export barrel” нужно создать файл “index.ts” со следующим кодом:

export * from './Button'
export * from './Icon'
export * from './Input'

Для использования этих компонентов на другой странице или в другом модуле импортируем их все сразу:

import { Button, Icon, Input } from '@/components'

Таким образом упрощается создание и редактирование кода, поскольку не нужно перечислять компоненты по отдельности. Более того, код становится более чистым и понятным, что крайне важно в больших проектах.

3. Именованный Export вместо “Export Default” 

Стоит отметить, что синтаксис “export barrel” может конфликтовать с экспортом по умолчанию (export default). В качестве примера вернемся к нашим компонентам:

export const Button = () => {
return <button>Button</button>
}
export default Buttonexport const Icon = () => {
return <svg>Icon</svg>
}
export default Iconexport const Input = () => {
return <input />
}
export default Input

Если каждый из этих компонентов находится в отдельном файле, а их нужно импортировать все сразу, то по умолчанию выполняют подобный импорт:

import Button from '@/components'
import Icon from '@/components'
import Input from '@/components'

Но это приводит к ошибкам, потому что JavaScript не может определить, какой “экспорт по умолчанию” нужно использовать. Приходится переписывать код:

import Button from '@/components/Button'
import Icon from '@/components/Icon'
import Input from '@/components/Input'

Однако при этом теряются преимущества “export barrel”. Для решения этой проблемы используйте именованный экспорт, который экспортирует без “default”:

import { Button, Icon, Input } from '@/components'

Еще одна важная проблема при “export default” связана с возможностью переименовывать импортируемые объекты. Однажды я дорабатывал проект React Native, в котором предыдущий разработчик везде использовал “export default”. Там были экраны “Login”, “Register” и “ForgotPassword”. Все три экрана имели лишь незначительные отличия. Проблема заключалась в том, что в конце каждого файла экрана было указано: “export default Login”. Это приводило к путанице, поскольку файл маршрута (Route File) был импортирован правильно:

import Login from '../../screens/Login'
import Register from '../../screens/Register'
import ForgotPassword from '../../screens/ForgotPassword'

// Далее — использование в маршрутах:

{
ResetPassword: { screen: ResetPassword },
Login: { screen: LoginScreen },
Register: { screen: RegisterScreen },
}

Но при открытии все эти файлы экспортировались с одинаковыми именами:

const login() {
return <>tela de login</>
}
export default Loginconst login() {
return <>tela de registro</>
}
export default Loginconst login() {
return <>tela de esqueci minha senha</>
}
export default Login

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

Поэтому я настоятельно рекомендую разработчикам использовать в основном “именованный экспорт”, а к ”экспорту по умолчанию” стоит прибегать только при крайней необходимости. Например, для Next.js и React.lazy. 

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

4. Правила формирования имен файлов

Представим, что имеется папка components со следующими файлами:

--components:
----Button.tsx
----Icon.tsx
----Input.tsx

Теперь предположим, что нужно разделить стили, логику или типы этих компонентов в обособленные файлы. Как назвать эти файлы? Очевидный вариант:

--components:
----Button.tsx
----Button.styles.css
----Icon.tsx
----Icon.styles.css
----Input.tsx
----Input.styles.css

Конечно, такой подход может показаться не очень понятным и упорядоченным, особенно когда нужно дополнительно разделить компонент на отдельные файлы, например логику или типы. Но как сохранить упорядоченность структуры? Вот решение:

--components:
----Button
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----Icon
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----Input
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx

Такой подход упрощает идентификацию предназначения и поиск каждого файла. Кроме того, используя Next.js или аналогичный фреймворк, можно адаптировать именование файлов, чтобы указать предназначение компонента для клиентской или серверной части. Например:

--components:
----RandomComponent
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.tsx
----RandomComponent2
------index.ts (exports everything necessary)
------types.ts
------styles.css
------utils.ts
------component.server.tsx

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

5. Правильно используем ESLint и Prettier для стандартизации кода

Когда над проектом работают более 10 разработчиков, каждый из них привносит в него свой стиль написания кода на основе прошлого опыта. Обеспечить в этом случае единообразие помогают такие инструменты, как ESLint и Prettier.

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

В свою очередь, ESLint помогает соблюдать правила написания кода. Например, он принудительно заставляет использовать анонимные функции вместо обычных, гарантирует правильное заполнение массива зависимостей React, позволяет запретить объявления “var“, используя лишь “let” и “const”, применять соглашения об именовании, например camelCase.

ESLint и Prettier способствуют соблюдению специфических правил в написании кода. Без них каждый разработчик может привносить в код собственный стиль, что впоследствии чревато вероятными сложностями в сопровождении проекта. Настройка этих инструментов  —  залог долговечности проекта благодаря структурированности и чистоте кода. Применение в работе ESLint и Prettier принесет огромную пользу всей команде и проекту в целом.

6. Husky и Lint  —  помощники в стандартизации кода

Определяемые ESLint и Prettier правила в некоторых ситуациях могут и не срабатывать. Поэтому настоятельно рекомендуется использовать Husky и Lint-Staged для более строго соответствия задаваемым нормам форматирования и синтаксиса кода.

Эти два инструмента очень важны в процессе разработки. Они позволяют настроить перед коммитом запуск ESLint и Prettier. Поэтому вы не сможете выполнить коммит, если код не соответствует установленным вами же правилам. Это настраиваемые инструменты для проверки кода перед отправкой его в репозиторий.

Кроме того, Husky поддерживает запуск других скриптов или действий перед коммитом или отправкой, что расширяет возможности автоматизации задач верификации и проверки качества кода.

Другим преимуществом Husky и Lint-Stageding является их интеграция с такими платформами, как GitHub. Это позволяет настроить автоматическое тестирование и проверки качества перед принятием запроса на извлечение. Вы сможете убедиться, что рассматриваемый код соответствует проектным нормам, минимизируя проблемы и обеспечивая единообразие.

ESLint, Prettier, Husky и Lint-Staged необходимы разработчикам для проверки кода перед выполнением коммитов. Комбинация из этих инструментов  —  это высокоэффективная методика стандартизации в проекте и поддержания качества кода.

7. Пользовательские хуки для повторно используемой логики

Вместе с React применяют навигационные хуки из таких библиотек, как react-router-dom, Next.js и react-navigation (для React Native). Однако из-за отсутствия конкретных данных о приложении у них ограниченные возможности. Более эффективным решением является создание собственных навигационных хуков с информацией обо всех страницах приложения, что упрощает навигацию между ними.

Вот пример такого хука:

import { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import type { Routes } from '@/types'

export const useRouter = () => {
const navigate = useCallback((path: Routes) => goTo(path), [goTo])

return { navigate }
}

Очевидная сложность в абстрагировании навигационного хука поначалу может вызывать некоторые затруднения. Однако в конечном итоге этот подход дает ряд преимуществ. Он упрощает вызов хука и автозаполнение функции, код становится более наглядным и понятным. Более того, упрощается сопровождение проекта. Например, если понадобится изменить библиотеку навигации, то изменения будут вноситься только в пользовательский хук, а не везде, где он используется.

Вариант с созданием пользовательских хуков может быть применен в приложении для управление файлами cookie, локальным хранилищем (localStorage), вызовами API и другими функциями. Этот простой подход для повторного использования логики в разных модулях проекта расширяет модульность и упрощает обслуживание кода.

8. Разница между хуками и служебными функциями

Важно понимать разницу между созданием хука и служебной функции, хотя разработчики часто путают эти два понятия. Для иллюстрации различий рассмотрим практический пример, который отражает менее удачный подход.

Содержимое папки Hooks:

export const useFormat = () => {
const formatHour = (date: number) => {
return new Date(date).toLocaleTimeString('pt', { timeStyle: 'short' })
}
// Другие функции форматирования даты/времени
return { formatHour }
}

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

Более правильный подход  —  создание служебной функции в выделенном каталоге, например Utils.

Содержимое папки Utils:

export const formatHour = (date: number) => {
return new Date(date).toLocaleTimeString('pt', { timeStyle: 'short' })
}

В этом случае служебная функция создается только один раз, а использоваться может в любом месте проекта независимо от контекста. Поскольку она не привязана к определенному компоненту, то не приводит к накладным расходам на создание хуков при каждом рендеринге.

Фундаментальное различие заключается в том, что хук  —  это компонент без визуальной части, созданный извне для React и вызываемый внутри определенного компонента, тогда как служебная функция, создаваемая только один раз вне контекста React, может использоваться где угодно. При выборе между хуками и служебными функциями следует учитывать конкретные потребности проекта и возможности для повторного использования кода.

9. useCallback и useMemo для оптимизации производительности

Хуки useCallback и useMemo могут по-разному влиять на производительность приложений React. useCallback нужен для запоминания функций, а useMemo для запоминания значений. При рендеринге компонентов оба хука предотвращают повторное создание функций или значений.

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

Избавиться от нее и повысить производительность приложения позволяет рациональное применение useCallback и useMemo для минимизации повторного использования функций и значений. Поскольку чрезмерное использование может наоборот иметь негативный эффект на производительность. Каждый вызов useCallback и useMemo включает два действия: одно для создания функции или значения, а другое для его запоминания. Кроме того, иногда некоторые функции или значения эффективнее воссоздать, чем запоминать.

Итак, используя useCallback и useMemo важно соблюдать осторожность, чтобы сохранить производительность и избежать трудно отслеживаемых ошибок. Если вы еще не сталкивались с этой серьезной проблемой, настоятельно рекомендую почитать подходящие статьи на эту тему, чтобы упростить отладку приложений React.

10. Логика разделения

Помимо учета различий между хуками и служебными функциями, крайне важно, чтобы код страницы был наиболее простым, с разделяемой по возможности логикой. При этом вы сможете улучшить структурированность и упростить сопровождение кода. Вот несколько полезных для этого методик.

Логика тостов. Если на странице необходимо отображать тосты, стоит создать для их отображения отдельный компонент и специальную функцию. Таким образом разделяются логика и представление на странице.

Вызовы API. Если на странице вызывается API, используйте такие библиотеки, как React Query, сохранив логику во внешнем хуке. Впоследствии страница может использовать этот хук, сохраняя более чистый и ориентированный на представление код.

Многократно используемые компоненты. Помещайте в глобальный каталог компоненты, которые повторно применяются в различных частях приложения. Если подобная организация усложняется, можно использовать шаблон проектирования компонентов, например Atomic Design. Разработка программного обеспечения  —  динамичная технология, в которой новые подходы могут появиться в любой момент. Поэтому оценивайте качество и эффективность этих подходов для конкретной ситуации.

Следуя этим практикам организации кода и постоянно обучаясь, вы сможете повысить эффективность работы и качество кода. Это, в свою очередь, еще больше повысит вашу ценность в сообществе разработчиков.

Заключение

Как известно, методики создания программного обеспечения непрерывно развиваются. Поэтому разработчик должен постоянно обучаться, адаптируясь к меняющимся требованиям. 

Рассмотренные в статье практики и концепции сделают код более эффективным и читабельным, упростят его обслуживание. Используя подобные рекомендации и постоянно совершенствуясь, вы сможете повысить свою квалификацию, продвигаясь по карьерной лестнице.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Renan Olovics: 10 Best Practices in Front-end Development (React)

Предыдущая статьяЧто такое Recover в Golang?
Следующая статьяПочему стоит использовать AVIF вместо JPEG, WebP, PNG и GIF в 2024 году