Skip to content

Latest commit

 

History

History
751 lines (586 loc) · 39.2 KB

react-philosophies.md

File metadata and controls

751 lines (586 loc) · 39.2 KB

React Philosophies 🤘

На главную

Источник  👀

1. Необходимый минимум

1.1. Когда машина умнее тебя

  1. Выполняй статический анализ кода с помощью ESLint. Используй плагин eslint-plugin-react-hooks для перехвата ошибок, связанных с неправильным использованием хуков:

Установка

npm i eslint-plugin-react-hooks --save-dev

# or
yarn add eslint-plugin-react-hooks --dev

Настройки eslint

{
  "extends": [
    // ...
    "plugin:react-hooks/recommended"
  ]
}

Или:

{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

При использовании create-react-app данный плагин включается в проект автоматически.

  1. Включай строгий режим. На дворе 2021 год, в конце концов.
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)
  1. Не обманывай React о зависимостях. Исправляй предупреждения/ошибки, связанные с зависимостями, возникающие при использовании хуков useEffect, useCallback и useMemo. Для обеспечения актуальности колбеков без выполнения лишних ререндеров можно использовать такой паттерн.

  2. Не забывай добавлять ключи при использовании map для рендеринга компонентов из массивов.

  3. Используй хуки только на верхнем уровне. Не вызывай их в циклах, условиях и вложенных функциях.

  4. Разберись с тем, что означает предупреждение Can't perform state update on unmounted component (невозможно обновить состояние размонтированного компонента).

  5. Избегай появления белого экрана смерти путем использования предохранителей на разных уровнях приложения. Предохранители можно также использовать для отправки сообщений в сервис мониторинга ошибок, такой как Sentry - статья о том, как это сделать.

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

  7. Не забывай про tree-shaking.

  8. Prettier (или похожие инструменты) избавляет от необходимости заботиться о единообразном форматировании кода за счет автоматизации этого процесса.

  9. TypeScript сделает твою жизнь намного проще.

  10. Для открытых проектов настоятельно рекомендую использовать Code Climate. Автоматическое обнаружение "дурно пахнущего" кода мотивирует меня на борьбу с техническим долгом.

  11. Next.js - отличный фреймворк.

1.2. Код - это необходимое зло

"Лучший код - это его отсутствие. Каждая новая строка кода, которую ты написал, это код, который кому-то придется отлаживать, код, который кому-то придется читать и понимать, код, который кому-то придется поддерживать." - Jeff Atwood

"Одним из самых продуктивных дней в моей жизни был день, когда я просто удалил 1000 строк кода." - Eric S. Raymond

TL;DR

  1. Думай перед добавлением новой зависимости.
  2. Пиши код в стиле, характерном для React.
  3. Не умничай! YAGNI

1.2.1. Думай перед добавлением новой зависимости.

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

Возможно, тебе это не нужно

  1. Действительно ли тебе нужен Redux? Может быть. Но не забывай, что React - сам по себе отличный инструмент для управления состоянием.

  2. Действительно ли тебе нужен Apollo Client? Apollo Client предоставляет большое количество прикольных "фич". Однако его использование существенно увеличивает размер сборки. Если в приложении используются фичи, которые не являются уникальными для Apollo Client, попробуй заменить его на такие библиотеки, как SWR или react-query(https://react-query.tanstack.com/comparison) (или обойтись вообще без библиотек).

  3. Axios? axios - отличная библиотека, возможности которой нелегко реализовать с помощью нативного fetch. Но если единственной причиной использования axios является более привлекательный интерфейс, реализуй обертку над fetch - статья о том, как это сделать.

  4. Lodash/Underscore? Вам не нужен lodash/underscore.

  5. Moment.js? Вам не нужен momentjs.

  6. Возможно, для "темизации" (режим light/dark) тебе не нужен контекст. Попробуй использовать для этого переменные CSS.

  7. Возможно, тебе не нужен JavaScript. Современный CSS - очень мощный инструмент. Вам не нужен JavaScript.

1.2.2. Пиши код в стиле, характерном для React.

  1. Избегай сложных условий и используй короткие вычисления.
  2. Вместо традиционных циклов (for, for in, for of и т.д.) используй цепочки из встроенных функций высшего порядка (map, filter, reduce, find, some и др.). Но не забывай, что в некоторых случаях традиционные циклы могут быть более производительными.

1.2.3. Не умничай!

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

Никогда так не делай! Реализуй только те вещи, которые нужны в данный момент. Не считай себя Нострадамусом.

1.3. Код, над которым поработал, должен стать лучше

Если ты встретил код, который "дурно пахнет", исправь его. Если исправить его сложно или на это нет времени, хотя бы пометь его комментарием (FIXME или TODO) с краткой характеристикой проблемы. Пусть все об этом знают. Это покажет другим, что тебе не все равно, а также добросовестное отношение к тому, что ты делаешь.

Примеры "дурно пахнущего" кода:

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

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

1.4. Ты можешь сделать лучше

  • setState (из useState) и dispatch (из useReducer) не нужно помещать в массив зависимостей таких хуков, как useEffect и useCallback, поскольку React гарантирует их стабильность.
// ❌
const decrement = useCallback(() => setCount(count - 1), [count, setCount])
const decrement = useCallback(() => setCount(count - 1), [count])

// ✅
const decrement = useCallback(() => setCount((c) => c - 1), [])
  • если useMemo или useCallback не имеют зависимостей, скорее всего, ты неправильно их используешь
// ❌
const MyComponent = (props) => {
  const fnToCall = useCallback((str) => `hello ${str || 'world'}`, [])
  const aConstant = useMemo(() => ({ x: 1, y: 3 }), [])

  // ...
}

// ✅
const A_CONSTANT = { x: 1, y: 3 }
const fnToCall = (str) => `hello ${str || 'world'}`
const MyComponent = (props) => {
  // ...
}
  • оборачивай кастомный контекст в хук: это не только сделает интерфейс лучше, но и позволит ограничиться 1 импортом
//
import { useContext } from 'react'
import { SomeContext } from './context'

export default function App() {
  const somethingFromContext = useContext(SomeContext)
  // ...
}

//
export function useSomeContext() {
  const context = useContext(SomeContext)
  if (!context) {
    throw new Error('useSomeContext может использоваться только внутри SomeProvider')
  }
}

import { useSomeContext } from './context'

export default function App() {
  const somethingFromContext = useSomeContext()

  // ...
}
  • думай о том, как компонент будет использоваться, перед тем, как писать код

2. Дизайн для счастья

"Любой дурак может писать код, который понимает компьютер. Хорошие программисты пишут код, который понимают люди." - Martin Fowler

"Соотношение времени, которое мы тратим на чтение кода, и времени, которое мы тратим на написание кода, составляет 10 к 1. Мы постоянно читаем старый код перед тем, как писать новый. Поэтому если ты хочешь быстро писать код, быстро выполнять задачи, если хочешь, чтобы писать код было легко, пиши такой код, который будет легко читать." - Robert C. Martin

TL;DR

  1. Избегай сложного состояния за счет удаления лишнего.
  2. Передавай банан, а не гориллу, держащую банан, вместе с джунглями (старайся передавать примитивы в качестве пропов).
  3. Компоненты должны быть маленькими и простыми - принцип единственной ответственности!
  4. Дублирование лучше плохой абстракции (избегай преждевременных / неуместных обобщений).
  5. Решай проблему передачи пропов за счет композиции. Context не единственное решение проблемы распределения состояния.
  6. Разделяй большие useEffect на несколько небольших.
  1. Извлекай логику в хуки и вспомогательные функции.
  2. Большой компонент лучше разделить на logical и presentational компоненты (не обязательно, зависит от ситуации).
  3. Старайся, чтобы в зависимостях useEffect, useCallback и useMemo находились только примитивы.
  4. Зависимостей этих хуков не должно быть слишком много.
  5. Вместе нескольких useState лучше использовать один useReducer, особенно в случае, когда некоторые значения состояния зависят от других значений или предыдущего состояния.
  6. Context не должен быть глобальным для всего приложения.Он должен находиться максимально близко к компонентам, потребляющим контекст.

2.1. Избегай ненужных состояний

Пример 1

Задача

Отобразить следующую информацию о треугольнике:

  • длину каждой стороны
  • гипотенузу
  • периметр
  • площадь

Треугольник - это объект { a: number, b: number } из API. a и b - две короткие стороны правильного треугольника.

Плохое решение

const Triangle = () => {
  const [triangleInfo, setTriangleInfo] = useState<{a: number, b: number} | null>(null)
  const [hypotenuse, setHypotenuse] = useState<number | null>(null)
  const [perimeter, setPerimeter] = useState<number | null>(null)
  const [area, setArea] = useState<number | null>(null)

  useEffect(() => {
    fetchTriangle().then((t) => setTriangleInfo(t))
  }, [])

  useEffect(() => {
    if (!triangleInfo) return

    const { a, b } = triangleInfo
    const h = computeHypotenuse(a, b)
    setHypotenuse(h)
    const _area = computeArea(a, b)
    setArea(newArea)
    const p = computePerimeter(a, b, h)
    setPerimeter(p)
  }, [triangleInfo])

  if (!triangleInfo) return null

  // рендеринг
}

Решение получше

const Triangle = () => {
  const [triangleInfo, setTriangleInfo] = useState<{a: number, b: number} | null>(null)

  useEffect(() => {
    fetchTriangle().then((t) => setTriangle(t))
  }, [])

  if (!triangleInfo) return null

  const { a, b } = triangleInfo
  const h = computeHypotenuse(a, b)
  const area = computeArea(a, b)
  const p = computePerimeter(a, b, h)

  // рендеринг
}

Пример 2

Задача

Разработать компонент, который будет делать следующее:

  • получать список уникальных точек от API
  • предоставлять кнопку для сортировки точек по x или y (по возрастанию)
  • предоставлять кнопку для изменения maxDistance (каждый раз увеличивая его значение на 10, начальным значением должно быть 100)
  • отображать только те точки, которые находятся не дальше maxDistance от центра (0, 0)
  • предположим, что список включает всего 100 точек, поэтому о производительности можно не беспокоиться (в случае большого количества элементов, можно мемоизировать некоторые вычисления с помощью useMemo)

Плохое решение

type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? 'y' : 'x'

const Points = () => {
  const [points, setPoints] = useState<{x: number, y: number}[]>([])
  const [filteredPoints, setFilteredPoints] = useState<{x: number, y: number}[]>([])
  const [sortedPoints, setSortedPoints] = useState<{x: number, y: number}[]>([])
  const [maxDistance, setMaxDistance] = useState<number>(100)
  const [sortBy, setSortBy] = useState<SortBy>('x')

  useEffect(() => {
    fetchPoints().then((r) => setPoints(r))
  }, [])

  useEffect(() => {
    const sorted = sortPoints(points, sortBy)
    setSortedPoints(sorted)
  }, [points, sortBy])

  useEffect(() => {
    const filtered = sortedPoints.filter((p) => getDistance(p.x, p.y) < maxDistance)
    setFilteredPoints(filtered)
  }, [sortedPoints, maxDistance])

  const otherSortBy = toggle(sortBy)
  const pointsToDisplay = filteredPoints.map((p) => <li key={`${p.x}-${p.y}`}>({p.x}, {p.y})</li>)

  return (
    <>
      <button onClick={() => setSortBy(otherSortBy)}>
        Сортировать по: {otherSortBy}
      <button>
      <button onClick={() => setMaxDistance(maxDistance + 10)}>
        Увеличить максимальную дистанцию
      <button>
      <p>Отображаются точки, которые находятся ближе, чем {maxDistance} от центра (0, 0).<br />
      Точки отсортированы по: {sortBy} (по возрастанию)</p>
      <ol>{pointToDisplay}</ol>
    </>
  )
}

Решение получше

// Обратите внимание: здесь также можно использовать `useReducer`
type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x'

const Points = () => {
  const [points, setPoints] = useState<{x: number, y: number}[]>([])
  const [maxDistance, setMaxDistance] = useState<number>(100)
  const [sortBy, setSortBy] = useState<SortBy>('x')

  useEffect(() => {
    fetchPoints().then((r) => setPoints(r))
  }, [])

  const otherSortBy = toggle(sortBy)
  const filteredPoints = points.filter((p) => getDistance(p.x, p.y) < maxDistance)
  const pointToDisplay = sortPoints(filteredPoints, sortBy).map((p) => <li key={`${p.x}|${p.y}`}>({p.x}, {p.y})</li>)

  return (
    <>
      <button onClick={() => setSortBy(otherSortBy)}>
        Сортировать по: {otherSortBy}
      <button>
      <button onClick={() => setMaxDistance(maxDistance + 10)}>
        Увеличить максимальную дистанцию
      <button>
      <p>Отображаются точки, которые находятся ближе, чем {maxDistance} от центра (0, 0).<br />
      Точки отсортированы по: {sortBy} (по возрастанию)</p>
      <ol>{pointToDisplay}</ol>
    </>
  )
}

2.2. Передавай банан

"Хотел банан, а получил гориллу, держащую банан, вместе с джунглями." - Joe Armstrong

В качестве пропов должны передаваться примитивы (по-возможности).

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

Пример

Задача

Создать компонент MemberCard, включающий 2 компонента: Summary и SeeMore. MemberCard принимает проп id. MemberCard использует хук useMember для получения следующей информации на основе id:

type Member = {
  id: string
  firstName: string
  lastName: string
  title: string
  imgUrl: string
  webUrl: string
  age: number
  bio: string
  // еще 100 полей
}

Компонент SeeMore должен отображать bio и age member, а также предоставлять кнопку для показа и скрытия этой информации.

Компонент Summary должен отображать изображение member. Также он должен отображать title, firstName и lastName. Клик по имени member должен перенаправлять на его профиль. Summary также может иметь дополнительный функционал.

Плохое решение

const Summary = ({ member }: { member: Member }) => {
  return (
    <>
      <img src={member.imgUrl} />
      <a href={member.webUrl} >
        {member.title}. {member.firstName} {member.lastName}
      </a>
    </>
  )
}

const SeeMore = ({ member }: { member: Member }) => {
  const [seeMore, setSeeMore] = useState<boolean>(false)
  return (
    <>
      <button onClick={() => setSeeMore(!seeMore)}>
        {seeMore ? "Скрыть" : "Показать"}
      </button>
      {seeMore && <>Возраст: {member.age} | Биография: {member.bio}</>}
    </>
  )
}

const MemberCard = ({ id }: { id: string }) => {
  const member = useMember(id)
  return <><Summary member={member} /><SeeMore member={member} /></>
}

Решение получше

const Summary = ({ imgUrl, webUrl, header }: { imgUrl: string, webUrl: string, header: string }) => {
  return (
    <>
      <img src={imgUrl} />
      <a href={webUrl}>{header}</a>
    </>
  )
}

const SeeMore = ({ componentToShow }: { componentToShow: ReactNode }) => {
  const [seeMore, setSeeMore] = useState<boolean>(false)
  return (
    <>
      <button onClick={() => setSeeMore(!seeMore)}>
        {seeMore ? "Скрыть" : "Показать"}
      </button>
      {seeMore && <>{componentToShow}</>}
    </>
  )
}

const MemberCard = ({ id }: { id: string }) => {
  const { title, firstName, lastName, webUrl, imgUrl, age, bio } = useMember(id)
  const header = `${title}. ${firstName} ${lastName}`
  return (
    <>
      <Summary {...{ imgUrl, webUrl, header }} />
      <SeeMore componentToShow={<>Возраст: {age} | Биография: {bio}</>} />
    </>
  )
}

2.3. Компоненты должны быть маленькими и простыми

Что означает принцип единственной ответственности?

Это означает, что компонент должен решать только одну задачу.

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

Как понять, что компонент отвечает данному критерию?

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

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

Пример

Задача

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

  • Нажатие каждой кнопки приводит к открытию модального окна, где можно выбирать и "сохранять" товары
  • При бронировании товара рядом с соответствующей кнопкой должна отображаться галочка
  • Пользователь должен иметь возможность редактировать заказ (добавлять или удалять товары) даже после бронирования
  • При наведении на кнопку курсора должен отображаться компонент WavingHand
  • Кнопка должна блокироваться при отсутствии товаров определенной категории
  • При наведении курсора на заблокированную кнопку должна отображаться подсказка Недоступно
  • Фон заблокированной кнопки должен быть gray
  • Фон "забронированной" кнопки должен быть green
  • Фон обычной кнопки должен быть red
  • Кнопка для каждой категории должна иметь уникальную подпись и иконку

Плохое решение

type ShopCategoryTileProps = {
  isBooked: boolean
  icon: ReactNode
  label: string
  componentInsideModal?: ReactNode
  items?: { name: string, quantity: number }[]
}

const ShopCategoryTile = ({
  icon,
  label,
  items
  componentInsideModal,
}: ShopCategoryTileProps ) => {
  const [openDialog, setOpenDialog] = useState(false)
  const [hover, setHover] = useState(false)
  const disabled = !items || items.length  === 0

  return (
    <>
      <Tooltip title="Недоступно" show={disabled}>
        <StyledButton
          className={disabled ? "gray" : isBooked ? "green" : "red" }
          disabled={disabled}
          onClick={() => disabled ? null : setOpenDialog(true) }
          onMouseEnter={() => disabled ? null : setHover(true)}
          onMouseLeave={() => disabled ? null : setHover(false)}
        >
          {icon}
          <StyledLabel>{label}<StyledLabel/>
          {!disabled && isBooked && <FaCheckCircle/>}
          {!disabled && hover && <WavingHand />}
        </StyledButton>
      </Tooltip>
      {componentInsideModal &&
        <Dialog open={openDialog} onClose={() => setOpenDialog(false)}>
          {componentInsideModal}
        </Dialog>
      }
    </>
  )
}

Решение получше

// разделяем компонент на 2 штуки
const DisabledShopCategoryTile = ({ icon, label }: { icon: ReactNode, label: string }) => {
  return (
    <Tooltip title="Недоступно">
      <StyledButton disabled={true} className="gray">
        {icon}
        <StyledLabel>{label}<StyledLabel/>
      </Button>
    </Tooltip>
  )
}

type ShopCategoryTileProps = {
  icon: ReactNode
  label: string
  isBooked: boolean
  componentInsideModal: ReactNode
}

const ShopCategoryTile = ({
  icon,
  label,
  isBooked,
  componentInsideModal,
}: ShopCategoryTileProps ) => {
  const [opened, setOpened] = useState(false)
  const [hovered, setHovered] = useState(false)
  const open = useCallback(() => setOpenDialog(!opened), [])
  const hover = useCallback(() => setHover(!hovered), [])

  return (
    <>
      <StyledButton
        disabled={false}
        className={isBooked ? "green" : "red"}
        onClick={open}
        onMouseEnter={hover}
        onMouseLeave={hover}
      >
        {icon}
        <StyledLabel>{label}<StyledLabel/>
        {isBooked && <FaCheckCircle/>}
        {hovered && <WavingHand />}
      </StyledButton>
      {opened &&
        <Dialog onClose={open}>
          {componentInsideModal}
        </Dialog>
      }
    </>
  )
}

Еще одно плохое решение из одного реального продакшна

const ShopCategoryTile = ({
  item,
  offers,
}: {
  item: ItemMap;
  offers?: Offer;
}) => {
  const dispatch = useDispatch();
  const location = useLocation();
  const history = useHistory();
  const { items } = useContext(OrderingFormContext)
  const [openDialog, setOpenDialog] = useState(false)
  const [hover, setHover] = useState(false)
  const isBooked =
    !item.disabled && !!items?.some((a: Item) => a.itemGroup === item.group)
  const isDisabled = item.disabled || !offers
  const RenderComponent = item.component

  useEffect(() => {
    if (openDialog && !location.pathname.includes("item")) {
      setOpenDialog(false)
    }
  }, [location.pathname]);
  const handleClose = useCallback(() => {
    setOpenDialog(false)
    history.goBack()
  }, [])

  return (
    <GridStyled
      xs={6}
      sm={3}
      md={2}
      item
      booked={isBooked}
      disabled={isDisabled}
    >
      <Tooltip
        title="Недоступно"
        placement="top"
        disableFocusListener={!isDisabled}
        disableHoverListener={!isDisabled}
        disableTouchListener={!isDisabled}
      >
        <PaperStyled
          disabled={isDisabled}
          elevation={isDisabled ? 0 : hover ? 6 : 2}
        >
          <Wrapper
            onClick={() => {
              if (isDisabled) {
                return;
              }
              dispatch(push(ORDER__PATH));
              setOpenDialog(true);
            }}
            disabled={isDisabled}
            onMouseEnter={() => !isDisabled && setHover(true)}
            onMouseLeave={() => !isDisabled && setHover(false)}
          >
            {item.icon}
            <Typography variant="button">{item.label}</Typography>
            <CheckIconWrapper>
              {isBooked && <FaCheckCircle size="26" />}
            </CheckIconWrapper>
          </Wrapper>
        </PaperStyled>
      </Tooltip>
      <Dialog fullScreen open={openDialog} onClose={handleClose}>
        {RenderComponent && (
          <RenderComponent item={item} offer={offers} onClose={handleClose} />
        )}
      </Dialog>
    </GridStyled>
  )
}

2.4. Дублирование лучше плохой абстракции

Избегайте преждевременной / неуместной генерализации. Если реализация простой фичи требует написания большого количества кода, изучите другие способы ее реализации. Советую взглянуть на The Wrong Abstraction.

3. Советы по производительности

"Преждевременная оптимизация - корень зла." - Tony Hoare

"Одно точное измерение лучше тысячи экспертных мнений." - Grace Hopper

TL;DR

  1. Если тебе кажется, что что-то работает медленно, убедись в этом с помощью специальных инструментов, например, профилировщика инструментов React-разработчика.
  2. Используй useMemo только для дорогих вычислений.
  3. Используй memo, useMemo и useCallback для предотвращение повторного рендеринга. При этом, количество зависимостей должно быть небольшим, а сами зависимости, преимущественно, должны быть примитивами.
  4. Убедись, что memo, useMemo и useCallback решают задачу (действительно ли они предотвращают ререндеринг?).
  5. Почини медленный рендеринг перед тем, как чинить ререндеринг.
  6. Размещай состояние максимально близко к компонентам, которые его используют. Это облегчит изучение кода и сделает приложение быстрее (колокация или совместное размещение состояний).
  7. Context должен быть логически отделен от остальной части приложения. Через провайдер должно передаваться минимально возможное количество значений. Это объясняется тем, что компоненты, потребляющие контекст, будут подвергаться повторному рендерингу при изменении любого значения, содержащегося в контексте, даже если они не используют это значение (кажется, что эту проблему можно решить с помощью useMemo).
  8. Контекст можно оптимизировать посредством разделения state и dispatch.
  9. Разберись с ленивой загрузкой и разделением кода.
  10. "Виртуализируй" списки (с помощью react-virtual или похожих решений).
  11. Меньший размер сборки, как правило, означает более быстрое приложение.
  12. Какую библиотеку для работы с формами использовать? Никакую. Но если очень хочется, то советую взглянуть на react-hook-form.