Задонатить и смотреть →
Открой безлимитный доступ к 100+ полезных скринкастов и получай скидки на все предстоящие мероприятия

Локальное состояние и хуки

Вот мы построили приложение на React с глобальным состоянием в хранилище Redux. Но пока есть неудобства. Загрузка лотов производится даже когда мы не выводим их на странице, вхолостую занимая ресурсы браузера. И ещё использование внешнего хранилища порой может быть лишним.

Сегодня рассмотрим варианты хранения локального состояния внутри компонента. Код побочных эффектов сделаем выполняемым только при необходимости. И рассмотрим внутреннюю работу хуков React для использования побочных эффектов в функциональных компонентах:

  • 00:00:48 - Постановка задачи
  • 00:02:20 - Глобальное и локальное состояния
  • 00:04:10 - Классовый компонент
  • 00:05:33 - Ручное использование полей
  • 00:09:15 - Локальное состояние в Component
  • 00:11:18 - Создание таймера
  • 00:13:37 - Вынесение контейнерного компонента
  • 00:16:09 - Плюсы и минусы
  • 00:17:49 - Мотивация введения хуков
  • 00:19:45 - Состояние через хук useState
  • 00:21:53 - Устройство и работа хуков
  • 00:26:08 - Изменение локального состояния
  • 00:26:55 - Компонент React.Fragment
  • 00:29:49 - Побочные эффекты в useEffect
  • 00:36:48 - Перенос загрузки лотов
  • 00:39:05 - Уточнение зависимости
  • 00:40:04 - Добавление флага загрузки
  • 00:42:16 - Вынесение подписки на цены
  • 00:44:28 - Отписка от уведомлений
  • 00:47:57 - Вынесение контейнеров
  • 00:49:20 - Многошаговый процесс подгрузки
  • 00:55:52 - Локальное хранение лотов
  • 00:57:17 - Атомарность изменения состояния
  • 00:59:49 - Возврат процедур в компонент
  • 01:02:10 - Сравнение подходов
  • 01:03:31 - Замена Redux на useReducer
  • 01:05:32 - Обзор результата
  • 01:06:47 - Комбинация подходов
  • 01:09:09 - Обзор результата

А в следующем эпизоде рассмотрим маршрутизацию через History API браузера.

Скрытый контент (код, слайды, ...) для подписчиков. Открыть →
Дмитрий Елисеев
elisdn.ru
Комментарии (18)
Александр Кулик

Супер

Ответить
Arunas

Спасибо.

Ответить
Кропотов Александр

Спасибо! Вообще получается что классы или функциональные компоненты это разные парадигмы разработки на Реакте, и мне кажется идет уход от классов да и от Редакса. Многие вещи решаются с помощью кастомных хуков, например та же авторизация. Вот тут много примеров есть https://usehooks.com, кому интересно. Т.е глобальные данные упаковываются либо в оборачивающий компонент, ну или в компонент высшего порядка. Но конечно, это вопрос личных предпочтений, как писать. Но вообще это чем то напомнило мидлвары в бэкенде.

Ответить
Дмитрий Елисеев

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

А по хранению данных часто используют локальное состояние и контексты. Но многие крупные проекты всё равно делают со внешними хранилищами если им требуется выделенная централизованная модель данных.

В этом эпизоде заметно, что простой виджет Clock спокойно перешёл на локальное состояние. Но как только мы всё состояние с лотами и экшены со stream и api перенесли внутрь Lots этот компонент сразу стал монструозным и почти нетестируемым. Так что не всегда очевидно что будет удобнее.

Ответить
rodigy

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

Ответить
Дмитрий Елисеев

То есть этот компонент:

function ClockContainer () {
  const [time, setTime] = React.useState(new Date())

  React.useEffect(() => {
    const interval = setInterval(() => setTime(new Date()), 1000)
    return () => clearInterval(interval)
  }, [setTime])

  return <Clock time={time} />
}

Выглядит более громоздким, чем этот?

class ClockContainer extends React.Component {
  constructor () {
    super()
    this.state = {
      time: new Date()
    }
    this.tick = this.tick.bind(this)
  }

  tick () {
    this.setState({
      time: new Date() 
    })
  }

  componentDidMount () {
    this.interval = setInterval(this.tick, 1000)
  }

  componentWillUnmount () {
    clearInterval(this.interval)
  }

  render () {
    return <Clock time={this.state.time} />
  }
}
Ответить
rodigy

В классах кода больше, но и ясности больше что происходит

Ответить
Aёct'ann

Но если знать, как работают хуки, то и в функциональном компоненте все предельно ясно

Ответить
Андрей

Дмитрий, спасибо за уроки, подскажите что за шрифт в самой программе idea?

Ответить
Дмитрий Елисеев

Интерфейс шрифтом Tahoma
В редакторе DejaVu Sans Mono

Ответить
Андрей

Благодарю

Ответить
kashamamina

А если вот будет отдельная страница с лотом, куда нужно будет вывести данные о лоте, то как это правильно сделать? Получается, будет компонент LotPage и он будет с адресной строки брать айди лота? А потом нужно сделать запрос на сервер чтобы получить этот лот, но где именно мы будем это делать, это же вроде как не экшен (в плане что вместе с экшенами мы его не поместим), состояние он никак не меняет, но запрос асинхронный? Плюс что если загрузка лота будет тоже такая продвинутая, как и у лотов?

Я думал что можно этот запрос сделать эффектом, вынести в контейнерный компонент с локальным состояниям где будет также вся эта продвинутая загрузка, но а что если я захочу протестировать этот эффект, вынести его куда-то и куда тогда? Так же каким образом этот контейнерный компонент будет получать доступ к объекту api, через пропсы его передавать?

Как бы вы это сделали?

Ответить
Дмитрий Елисеев

Да, LotPage в плане состояния и загрузки будет работать абсолютно также как Lots. Он будет брать id из маршрутизатора и у себя подгружать текущий лот.

Если с api всё будет в одном компоненте, то тогда со всеми хуками нужно будет тестировать весь компонент через React Testing Library. И там подменять переменную global.fetch или модуль api на заглушку, как мы делали в эпизоде про регистрацию.

Ответить
kashamamina

спасибо

Ответить
S.Polessky

Несколько вопросов по реализации функции resolveDispatcher() (Тайминг: 22:16) (Скриншот)

Интересно, для чего поступили именно так:

1) Зачем такая "сложная" проверка в if с использованием инверсии? Короче, читабельный же: if (dispatcher === null).

2) Зачем выброс исключения обернули в отдельную область видимости {}?

3) Почему используют var, а не let? Для совместимости?

Ответить
Дмитрий Елисеев

Да, весьма странный подход. Надо спросить у авторов этого кода.

Ответить
Анна

Спасибо! Не понимаю, а чем плохи классы-компоненты? Все ради экономии пары строк кода и ТОЛЬКО поэтому или есть более весомые аргументы против? А чем плох redux?)

Ответить
Дмитрий Елисеев

Все ради экономии пары строк кода?

Да, для экономии десятка строк кода.

А чем плох redux?

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

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

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Google
GitHub
Yandex
MailRu