5. React правила

5. React правила

5.1. Декомпозиция

  • Давайте условимся, что компоненты не должны быть размером более 300 строк кода. Лучше, конечно, меньше.
  • Лучше передробить, чем недодробить

5.2. Структура компонент

Чтобы все файлы компонентов были согласованы, следуйте следующей схеме:

// 1. Imports
import { useEffect, useState } from 'react'
 
// 2. Types
type Props = {
  todolist: TodolistDomain
  tasks: Task[]
}
 
// 3. Additional variables
// Если константа используется только в данной компоненте, то выносим ее за пределы компонента.
// Если используется во многих местах приложения, то выносим ее в `common/constants`
const SOME_CONSTANT = 'something'
 
// 4. Деструктуризация пропсов
export const Todolist = ({ todolist, tasks }: Props) => {
  // ❗ Данную логику лучше выносить в кастомный хук
  // 4.1. Объявление переменных
  // 4.2. Разделяем логически связанные части
  const [state, setState] = useState(false)
  const [user, setUser] = useState<string | null>(null)
 
  const { fetchTasks, addTask } = useActions(tasksThunks)
 
  const { token } = useParams<{ token?: string }>()
 
  // 4.3. Effects (нету строгого правила)
  // * useEffect можно перенести вниз (перед TSX)
  useEffect(() => {
    fetchTasks(todolist.id)
  }, [])
 
  // 4.4. Функции
  const addTaskCallBack = (title: string) => {
    return addTask({ title, todolistId: todolist.id }).unwrap()
  }
 
  // 4.5. Функции обработчики называем с handler в конце
  const testHandler = () => {
    // code
  }
 
  return (
    <>
      <TodolistTitle todolist={todolist} />
      <AddItemForm addItem={addTaskCallBack} />
      <Tasks todolist={todolist} tasks={tasks} />
      <FilterTasksButtons todolist={todolist} />
      <button onClick={testHandler}>test</button>
    </>
  )
}

5.3. Типизация пропсов

Для типизация Props не используем React.FC

❌ Пример с ипользованием React.FC
import { FC } from 'react'
 
type Props = {
  message: string
}
 
const Component: FC<Props> = ({ message }) => {
  return <>TSX</>
}
✅ Пример без ипользования React.FC
type Props = {
  message: string
}
 
const Component = ({ message }: Props) => {
   return <>TSX</>
}

5.3.1. Теория

Что React.FC добавляет в функциональный компонент ?

  • явно указывает возвращаемое значение (ReactNode)
  • добавляет типы для статический свойств (displayName, propsTypes, defaultProps)
type FC<P = {}> = FunctionComponent<P>
 
interface FunctionComponent<P = {}> {
    (props: P, context?: any): ReactNode;
    propTypes?: WeakValidationMap<P> | undefined;
    contextTypes?: ValidationMap<any> | undefined;
    defaultProps?: Partial<P> | undefined;
    displayName?: string | undefined;
}

5.3.2. Минусы

  • Лишний импорт (import { FC } from 'react')
  • Приходится писать дополнительный дженерик, дополнительная “сущность” в коде
  • В современных руководствах от него отказались (пруфы ниже)

5.3.3. Плюсы

  • React.FC типизирует возвращаемое значение как ReactNode (поможет, если вы захотите вернуть из компонента объект)
  • Явно показывает, что функция является React component

5.3.4. Статьи на данную тему:

5.4. Кастомный хук (opens in a new tab)

❗ Если логики в компоненте больше 20 строк выносим логику в кастомный хук

Кастомный хук - это функция, которая обычно начинается с префикса "use" (например, useCustomHook), и она может содержать в себе логику состояния, эффектов (side-effects), или другую функциональность, которую вы хотите использовать в ваших компонентах.

❗Преимущества использования кастомных хуков:

  • Повторное использование логики. Когда вы создаете кастомный хук, вы можете использовать его в нескольких компонентах, что позволяет избежать дублирования кода.
  • Сокрытие деталей. Кастомный хук позволяет сокрыть детали реализации логики внутри него, делая код компонентов более чистым и понятным.
  • Упрощение компонентов. Используя кастомный хук, вы можете вынести сложную логику из компонента, делая его более простым и легко поддерживаемым.
Login.tsx
const Login = () => {
    const isLoggedIn = useSelector(selectIsLoggedIn);
 
    const {formik} = useLogin();
 
    if (isLoggedIn) {
        return <Navigate to = {"/"} />;
    }
 
    return (
        <div>JSX</div>
    );
}

5.5. Fragment (opens in a new tab)

Если, компонент на верхнем уровне не требует стилизации, то для того, чтобы не создавать лишнюю обертку используем Fragment (...)

// ❌
const ButtonList1 = () => {
  return (
    <div>
      <button>add</button>
      <button>remove</button>
    </div>
  )
}
 
// ✅
const ButtonList2 = () => {
  return (
    <>
      <button>add</button>
      <button>remove</button>
    </>
  )
}

5.6. Деструктуризация пропсов

Деструктурируем пропсы, чтобы писать меньше кода в разметке

// ❌
const Button1 = props => {
  return <button>{props.text}</button>
}
 
// ✅
const Button2 = ({ text }) => {
  return <button>{text}</button>
}

5.7. Дробим логические выражения

Если логическое выражение составное, то дробим его

// ❌
return (
    <>
        {(requiredAnswers && !!avatar && !isReminderOpen) || isHelp2Debug && <NavBar>}
    </>
)
 
// ✅
const navbarEnabled = (requiredAnswers && !!avatar && !isReminderOpen) || isHelp2Debug
 
return (
    <>
        { navbarEnabled && <NavBar>}
    </>
)

5.8. Оптимизация приложения

⭕ (in progress) Избыточная оптимизацию (useMemo, useCallback и пр.)

Передозировка useMemo (opens in a new tab)

5.9. Включайте только один React компонент в файл.

User.tsx
// ❌ Пример плохого использования. В одном файле 3 компонента
 
//1️⃣
const Component1 = ({ text }: Props) => {
  return <div>{text}</div>
}
 
// 2️⃣
const Component2 = ({ count }: Props) => {
  return <div>Count: {count}</div>
}
 
// 3️⃣
const User = () => {
  return (
    <div>
      <Component1 text={'lorem ipsum'} />
      <Component2 count={'10'} />
    </div>
  )
}
User.tsx
const User = () => {
  return (
    <div>
      <Component1 text={"lorem ipsum"}/>
      <Component2 count={"10"}/>
    </div>
  );
};