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

Управление состоянием

React и Recoil отвечают за управление состоянием в коде.

Используйте useRecoilState для хранения состояния

Полезно создавать столько атомов, сколько вам нужно для хранения состояния.
Лучше использовать дополнительные атомы, чем пытаться быть слишком кратким с передачей свойств.
export const myAtomState = atom({
  key: 'myAtomState',
  default: 'default value',
});

export const MyComponent = () => {
  const [myAtom, setMyAtom] = useRecoilState(myAtomState);

  return (
    <div>
      <input
        value={myAtom}
        onChange={(e) => setMyAtom(e.target.value)}
      />
    </div>
  );
}

Не используйте useRef для хранения состояния

Избегайте использования useRef для хранения состояния. Если вы хотите сохранить состояние, вам следует использовать useState или useRecoilState. Смотрите как управлять повторными рендерами, если вы считаете, что вам нужен useRef, чтобы предотвратить их.

Управление повторными рендерами

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

Работа на корневом уровне

Избежать повторных рендеров в новых функциях стало проще, устранив их на корневом уровне. Сайдкар-компонент PageChangeEffect содержит всего один useEffect, который реализует всю логику при изменении страницы. Таким образом, вы знаете, что есть только одно место, которое может вызвать повторный рендер.

Всегда думайте дважды, прежде чем добавлять useEffect в ваш код

Повторные рендеры часто вызываются ненужными useEffect. Подумайте, нужно ли вам использовать useEffect, или же вы можете перенести логику в функцию обработчика событий. Как правило, несложно перенести логику в функции handleClick или handleChange. Вы также можете найти их в библиотеках, таких как Apollo: onCompleted, onError и т. д.

Используйте дополнительный компонент для извлечения useEffect или логики получения данных

Если вы считаете, что нужно добавить useEffect в корневой компонент, стоит рассмотреть возможность его извлечения в сайдкар-компонент. Вы можете применять то же самое для логики получения данных, с хуками Apollo.
// ❌ 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 <div>{data}</div>;
};

export const App = () => (
  <RecoilRoot>
    <PageComponent />
  </RecoilRoot>
);
// ✅ 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 <div>{data}</div>;
};

export const PageData = () => {
  const [data, setData] = useRecoilState(dataState);
  const [someDependency] = useRecoilState(someDependencyState);

  useEffect(() => {
    if(someDependency !== data) {
      setData(someDependency);
    }
  }, [someDependency]);

  return <></>;
};

export const App = () => (
  <RecoilRoot>
    <PageData />
    <PageComponent />
  </RecoilRoot>
);

Используйте состояния семейства Recoil и селекторы семейства Recoil

Состояния семейства Recoil и селекторы — отличный способ избежать повторных рендеров. Они полезны, когда нужно хранить список элементов.

Не следует использовать React.memo(MyComponent)

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

Ограничьте использование useCallback или useMemo

Часто это не нужно и делает код труднее читаемым и поддерживаемым для улучшения производительности, которую сложно заметить.

Console.logs

console.log полезны во время разработки, предоставляя информацию в реальном времени о значениях переменных и потоке кода. Однако оставление их в коде для продакшена может привести к нескольким проблемам:
  1. Производительность: Избыточное логирование может повлиять на производительность, особенно в клиентских приложениях.
  2. Безопасность: Логирование конфиденциальных данных может раскрыть критическую информацию тем, кто может просматривать консоль браузера.
  3. Чистота: Заполнение консоли логами может скрыть важные предупреждения или ошибки, которые нужно увидеть разработчикам или инструментам.
  4. Профессионализм: Конечные пользователи или клиенты, проверяющие консоль и видящие множество логов, могут усомниться в качестве и обработке кода.
Убедитесь, что все console.logs удалены перед загрузкой кода в продакшен.

Называние

Наименование переменных

Названия переменных должны точно описывать их цель или функцию.

Проблемы с универсальными именами

Универсальные имена в программировании не идеальны, так как они не хватает определенности, что ведет к неясности и ухудшению читаемости кода. Такие имена не передают цель переменной или функции, делая трудным понимать намерение кода без более глубокого исследования. Это может привести к увеличению времени отладки, повышению вероятности ошибок и трудностям в поддержке и сотрудничестве. Между тем, описательные имена делают код самодокументирующимся и более простым для навигации, улучшая качество кода и продуктивность разработчиков.
// ❌ Bad, uses a generic name that doesn't communicate its
//    purpose or content clearly
const [value, setValue] = useState('');
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');

Некоторые слова, которых стоит избегать в названиях переменных

  • dummy

Обработчики событий

Названия обработчиков событий должны начинаться с handle, в то время как on используется как префикс для наименования событий в пропсах компонентов.
// ❌ Bad
const onEmailChange = (val: string) => {
  // ...
};
// ✅ Good
const handleEmailChange = (val: string) => {
  // ...
};

Необязательные пропсы

Избегайте передачи значения по умолчанию для необязательного пропса. ПРИМЕР Возьмите компонент EmailField, определенный ниже:
type EmailFieldProps = {
  value: string;
  disabled?: boolean;
};

const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
  <TextInput value={value} disabled={disabled} fullWidth />
);
Использование
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;

Компонент как пропсы

Постарайтесь максимально передавать неинстанцированные компоненты как пропсы, чтобы дети могли самостоятельно определять, какие пропсы им нужно передать. Наиболее распространенный пример этого — иконки компонентов:
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;

// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
  const theme = useTheme();

  return (
    <div>
      <MyIcon size={theme.icon.size.md}>
    </div>
  )
};
Чтобы React понял, что компонент является компонентом, нужно использовать PascalCase, чтобы потом инстанцировать его с <MyIcon>

Сверление пропсов: минимизируйте его

Сверление пропсов в контексте React — это практика передачи переменных состояния и их сеттеров через многие уровни компонентов, даже если промежуточные компоненты их не используют. Хотя иногда это необходимо, чрезмерное сверление пропсов может привести к:
  1. Уменьшение читаемости: Отслеживание происхождения пропса или мест, где он используется, может стать запутанным в глубоко вложенной структуре компонентов.
  2. Трудности в обслуживании: Изменения в структуре пропсов одного компонента могут потребовать изменений в нескольких компонентах, даже если они их не используют напрямую.
  3. Снижение повторного использования компонентов: Компонент, получающий много пропсов только для передачи их дальше, становится менее универсальным и сложным для повторного использования в разных контекстах.
Если вы считаете, что чрезмерно используете сверление пропсов, обратите внимание на лучшие практики управления состоянием.

Импорт

При импорте предпочтение стоит отдать указанным псевдонимам, а не полным или относительным путям. Псевдонимы
{
  alias: {
    "~": path.resolve(__dirname, "src"),
    "@": path.resolve(__dirname, "src/modules"),
    "@testing": path.resolve(__dirname, "src/testing"),
  },
}
Использование
// ❌ Bad, specifies the entire relative path
import {
  CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
  ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';

Проверка схемы

Zod — это проверка схемы для нетипизированных объектов:
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<typeof validationSchema>;

Изменения с нарушением совместимости

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