--- title: Лучшие практики image: /images/user-guide/tips/light-bulb.png --- Header Этот документ описывает лучшие практики, которых следует придерживаться при работе с фронтендом. ## Управление состоянием React и Recoil отвечают за управление состоянием в коде. ### Используйте `useRecoilState` для хранения состояния Полезно создавать столько атомов, сколько вам нужно для хранения состояния. Лучше использовать дополнительные атомы, чем пытаться быть слишком кратким с передачей свойств. ```tsx export const myAtomState = atom({ key: 'myAtomState', default: 'default value', }); export const MyComponent = () => { const [myAtom, setMyAtom] = useRecoilState(myAtomState); return (
setMyAtom(e.target.value)} />
); } ``` ### Не используйте `useRef` для хранения состояния Избегайте использования `useRef` для хранения состояния. Если вы хотите сохранить состояние, вам следует использовать `useState` или `useRecoilState`. Смотрите [как управлять повторными рендерами](#managing-re-renders), если вы считаете, что вам нужен `useRef`, чтобы предотвратить их. ## Управление повторными рендерами Управлять повторными рендерами в React может быть сложно. Вот некоторые правила, которые стоит соблюдать, чтобы избегать ненужных повторных рендеров. Помните, что вы всегда можете избежать повторных рендеров, если поймете причину их возникновения. ### Работа на корневом уровне Избежать повторных рендеров в новых функциях стало проще, устранив их на корневом уровне. Сайдкар-компонент `PageChangeEffect` содержит всего один `useEffect`, который реализует всю логику при изменении страницы. Таким образом, вы знаете, что есть только одно место, которое может вызвать повторный рендер. ### Всегда думайте дважды, прежде чем добавлять `useEffect` в ваш код Повторные рендеры часто вызываются ненужными `useEffect`. Подумайте, нужно ли вам использовать `useEffect`, или же вы можете перенести логику в функцию обработчика событий. Как правило, несложно перенести логику в функции `handleClick` или `handleChange`. Вы также можете найти их в библиотеках, таких как Apollo: `onCompleted`, `onError` и т. д. ### Используйте дополнительный компонент для извлечения `useEffect` или логики получения данных Если вы считаете, что нужно добавить `useEffect` в корневой компонент, стоит рассмотреть возможность его извлечения в сайдкар-компонент. Вы можете применять то же самое для логики получения данных, с хуками Apollo. ```tsx // ❌ Bad, will cause re-renders even if data is not changing, // because useEffect needs to be re-evaluated export const PageComponent = () => { const [data, setData] = useRecoilState(dataState); const [someDependency] = useRecoilState(someDependencyState); useEffect(() => { if(someDependency !== data) { setData(someDependency); } }, [someDependency]); return
{data}
; }; export const App = () => ( ); ``` ```tsx // ✅ Good, will not cause re-renders if data is not changing, // because useEffect is re-evaluated in another sibling component export const PageComponent = () => { const [data, setData] = useRecoilState(dataState); return
{data}
; }; export const PageData = () => { const [data, setData] = useRecoilState(dataState); const [someDependency] = useRecoilState(someDependencyState); useEffect(() => { if(someDependency !== data) { setData(someDependency); } }, [someDependency]); return <>; }; export const App = () => ( ); ``` ### Используйте состояния семейства Recoil и селекторы семейства Recoil Состояния семейства Recoil и селекторы — отличный способ избежать повторных рендеров. Они полезны, когда нужно хранить список элементов. ### Не следует использовать `React.memo(MyComponent)` Избегайте использования `React.memo()`, так как это не решает причину повторного рендера, но разрывает цепочку рендера, что может привести к неожиданному поведению и усложнить рефакторинг кода. ### Ограничьте использование `useCallback` или `useMemo` Часто это не нужно и делает код труднее читаемым и поддерживаемым для улучшения производительности, которую сложно заметить. ## Console.logs `console.log` полезны во время разработки, предоставляя информацию в реальном времени о значениях переменных и потоке кода. Однако оставление их в коде для продакшена может привести к нескольким проблемам: 1. **Производительность**: Избыточное логирование может повлиять на производительность, особенно в клиентских приложениях. 2. **Безопасность**: Логирование конфиденциальных данных может раскрыть критическую информацию тем, кто может просматривать консоль браузера. 3. **Чистота**: Заполнение консоли логами может скрыть важные предупреждения или ошибки, которые нужно увидеть разработчикам или инструментам. 4. **Профессионализм**: Конечные пользователи или клиенты, проверяющие консоль и видящие множество логов, могут усомниться в качестве и обработке кода. Убедитесь, что все `console.logs` удалены перед загрузкой кода в продакшен. ## Называние ### Наименование переменных Названия переменных должны точно описывать их цель или функцию. #### Проблемы с универсальными именами Универсальные имена в программировании не идеальны, так как они не хватает определенности, что ведет к неясности и ухудшению читаемости кода. Такие имена не передают цель переменной или функции, делая трудным понимать намерение кода без более глубокого исследования. Это может привести к увеличению времени отладки, повышению вероятности ошибок и трудностям в поддержке и сотрудничестве. Между тем, описательные имена делают код самодокументирующимся и более простым для навигации, улучшая качество кода и продуктивность разработчиков. ```tsx // ❌ Bad, uses a generic name that doesn't communicate its // purpose or content clearly const [value, setValue] = useState(''); ``` ```tsx // ✅ Good, uses a descriptive name const [email, setEmail] = useState(''); ``` #### Некоторые слова, которых стоит избегать в названиях переменных - dummy ### Обработчики событий Названия обработчиков событий должны начинаться с `handle`, в то время как `on` используется как префикс для наименования событий в пропсах компонентов. ```tsx // ❌ Bad const onEmailChange = (val: string) => { // ... }; ``` ```tsx // ✅ Good const handleEmailChange = (val: string) => { // ... }; ``` ## Необязательные пропсы Избегайте передачи значения по умолчанию для необязательного пропса. **ПРИМЕР** Возьмите компонент `EmailField`, определенный ниже: ```tsx type EmailFieldProps = { value: string; disabled?: boolean; }; const EmailField = ({ value, disabled = false }: EmailFieldProps) => ( ); ``` **Использование** ```tsx // ❌ Bad, passing in the same value as the default value adds no value const Form = () => ; ``` ```tsx // ✅ Good, assumes the default value const Form = () => ; ``` ## Компонент как пропсы Постарайтесь максимально передавать неинстанцированные компоненты как пропсы, чтобы дети могли самостоятельно определять, какие пропсы им нужно передать. Наиболее распространенный пример этого — иконки компонентов: ```tsx const SomeParentComponent = () => ; // In MyComponent const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => { const theme = useTheme(); return (
) }; ``` Чтобы React понял, что компонент является компонентом, нужно использовать PascalCase, чтобы потом инстанцировать его с `` ## Сверление пропсов: минимизируйте его Сверление пропсов в контексте React — это практика передачи переменных состояния и их сеттеров через многие уровни компонентов, даже если промежуточные компоненты их не используют. Хотя иногда это необходимо, чрезмерное сверление пропсов может привести к: 1. **Уменьшение читаемости**: Отслеживание происхождения пропса или мест, где он используется, может стать запутанным в глубоко вложенной структуре компонентов. 2. **Трудности в обслуживании**: Изменения в структуре пропсов одного компонента могут потребовать изменений в нескольких компонентах, даже если они их не используют напрямую. 3. **Снижение повторного использования компонентов**: Компонент, получающий много пропсов только для передачи их дальше, становится менее универсальным и сложным для повторного использования в разных контекстах. Если вы считаете, что чрезмерно используете сверление пропсов, обратите внимание на [лучшие практики управления состоянием](#state-management). ## Импорт При импорте предпочтение стоит отдать указанным псевдонимам, а не полным или относительным путям. **Псевдонимы** ```js { alias: { "~": path.resolve(__dirname, "src"), "@": path.resolve(__dirname, "src/modules"), "@testing": path.resolve(__dirname, "src/testing"), }, } ``` **Использование** ```tsx // ❌ Bad, specifies the entire relative path import { CatalogDecorator } from '../../../../../testing/decorators/CatalogDecorator'; import { ComponentDecorator } from '../../../../../testing/decorators/ComponentDecorator'; ``` ```tsx // ✅ Good, utilises the designated aliases import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator'; import { ComponentDecorator } from 'twenty-ui/testing'; ``` ## Проверка схемы [Zod](https://github.com/colinhacks/zod) — это проверка схемы для нетипизированных объектов: ```js const validationSchema = z .object({ exist: z.boolean(), email: z .string() .email('Email must be a valid email'), password: z .string() .regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'), }) .required(); type Form = z.infer; ``` ## Изменения с нарушением совместимости Всегда проводите тщательное ручное тестирование перед тем, как продолжить, чтобы гарантировать, что изменения не вызвали перебоев в других местах, поскольку тесты еще не были широко интегрированы.