React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
useLayoutEffect

Вариация useEffect, которая дает шанс вмешаться в рендеринг компонента.

useLayoutEffect запускается СИНХРОННО после всех изменений DOM, ДО ТОГО, как браузер осуществит их отрисовку. Это практически полный аналог классовых методов componentDidMount/componentDidUpdate.

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

#документация #хуки
useDebugValue

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

#документация #хуки #отладка
Объяснение хуков

Статья от создателя React Дэна Абрамова (англ.): https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

В принципе все, что он говорит, мы уже рассмотрели и в целом уяснили, но все-таки пропускать статью от основателя было бы неправильно.

Основные моменты:

- До появления хуков React позволял удобно разделять UI, но не логику. Хуки решают эту проблему без добавления дополнительных оберток.
- Добавление хуков не раздувает библиотеку. Напротив, их использование позволяет уменьшить размер бандла, так как хуки минифицируются лучше, чем классы.
- Хуки больше соответствуют философии React, чем классы, но от поддержки классов никто не отказывается, ничего переписывать не нужно.
- Никакой магии в хуках нет, они не сложнее, чем Array.push/Array.pop.
- Правила хуков хоть и выглядят несколько странно, но никак не помешают в работе.

#ссылки #хуки
React-хуки - это не магия, а обычные массивы

Статья Rudi Yardley (англ.): https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

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

#ссылки #хуки
Пример запроса данных с помощью хуков

Демо-пример: https://codesandbox.io/s/jvvkoo8pq3?file=/src/index.js

Тут все очень просто:

- Создается состояние data для массива результатов, изначально - пустой массив (хук useState)
- Создается состояние query для текста поискового запроса (хук useState)
- При изменении запроса (setQuery) происходит перерендер компонента - После рендера вызывается эффект, заданный с помощью хука useEffect (у него в зависимостях указана переменная query, значение которой изменилось, поэтому он вызывается)
- Функция-эффект запрашивает данные по апи, а когда приходит ответ, записывает его в состояние data (setData)
- Снова происходит перерендер, но в этот раз эффект не вызывается, так как его единственная зависимость query не изменилась

Пошаговый туториал: https://www.robinwieruch.de/react-hooks-fetch-data

Здесь все подробно разобрано:

- Почему в useEffect нельзя передавать асинхронную функцию
- Зачем нужен массив зависимостей и как он работает
- Зачем используется переменная ignore внутри эффекта (спойлер: чтобы не выводить данные предыдущего запроса, если после него уже отправлен новый)
- Добавление индикатора загрузки
- Обработка ошибок загрузки
- Добавление формы (form) и обработка события onSubmit

Кроме того описан пользовательский хук для извлечения данных useDataApi. Он принимает полный урл запроса, поэтому может работать с любым API. Этот хук инкапсулирует логику запроса данных, индикатора загрузки и обработки ошибок запроса.

Хук useDataApi на гитхабе: https://github.com/the-road-to-learn-react/use-data-api

Наконец в конце рассматривается кейс с заменой отдельных простых состояний (isError, isLoading, data) на единое сложное состояние с использованием хука useReducer. При этом в нужные моменты вызываются нужные события (FETCH_INIT, FETCH_SUCCESS, FETCH_FAILURE) с помощью метода dispatch, а редьюсер обрабатывает их и обновляет состояние.

#ссылки #хуки #примерыкода
Коллбэк-рефы в функциональных компонентах

Мы уже говорили о коллбэк-рефах здесь https://yangx.top/react_junior/42.

В двух словах: если в атрибут ref элемента передать функцию (вместо объекта, созданного методом React.createRef), она будет вызвана при монтировании этого элемента, а в качестве аргумента получит ссылку на него.

В функциональных компонентах так тоже можно делать, с одним условием - функция (коллбэк-реф) должна быть создана с помощью хука useCallback. В ином случае она будет меняться при каждом рендере.

#рефы #примерыкода #документация #хуки
Функции в списке зависимостей

Если мы указываем для хука список зависимостей (useEffect, useLayoutEffect, useMemo, useCallback, useImperativeHandle), в него должны войти все использованные значения, которые задействованы в потоке данных React, включая пропсы, состояние и их производные.

А что если одно из этих значений функция - внешняя по отношению к вызову хука? Нужно ли ее указывать в зависимостях к хуку useEffect. (см. прикрепленный скриншот)

С одной стороны - да, ведь она использует состояние count. Если count изменится, эффект должен быть вызван заново. Можно, конечно, указать в списке зависимостей сам count, но ведь функция foo может использовать еще какие-нибудь данные, можно что-то упустить.

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

- изменился count при клике на кнопку
- произошел перерендер
- функция foo создалась заново
- поэтому вызван эффект
- вызов foo изменяет состояние fooCount
- происходит перерендер
- функция foo создалась заново
- поэтому вызван эффект... 🤦‍♂️ (хотя не должен быть вызван, ведь count-то не изменился)

Варианты решения

👉 объявить foo снаружи компонента (при этом она не сможет ссылаться на пропсы и состояние)
👉 переместить foo внутрь эффекта (тогда она не будет пересоздаваться, и в зависимостях ее указывать не нужно)
👉 убедиться, что foo не использует никаких данных компонента, которые могут измениться при перерендере (и тогда не указывать ее в зависимостях)
👉 создать foo с использованием хука useCallback и указанием зависимостей (тогда ее идентичность не будет меняться между рендерами)

https://codepen.io/furrycat/pen/MWmKKwZ?editors=0011

#ошибки #примерыкода #хуки
Инициализация состояния

Хук useState может принимать функцию вместо дефолтного значения. Гарантируется, что эта функция будет вызвана всего один раз при первом рендере. Таким образом можно инициализировать сложные состояния - затратные операции будут выполнены лишь один раз.

#хуки #документация #важно
👍1
Передача коллбэков дочерним компонентам

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

dispatch даже можно передать вниз с помощью контекста, чтобы избежать пробрасывания пропсов на несколько уровней.

#важно #документация #хуки
Тестирование в React

Открываем новый большой раздел - тестирование React-приложений.

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

Так как речь идет о фронтенде и интерфейсах, тестировать придется не только логику, но и верстку. И экосистема React уже имеет готовые решения для этого.

Мы будем разбираться, как тестировать React-приложения с использованием библиотек Jest (исполнитель тестов) и React Testing Library (набор вспомогательных функций).

#документация #тестирование
Jest

Jest - это тестовый фреймворк для JavaScript-кода. Create React App использует Jest из коробки.

Возможности Jest

👉 подмена области рендеринга с помощью jsdom
тесты будут выполняться вне настоящего браузера, следовательно его поведение нужно имитировать.

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

👉 фиктивные модули
позволяет заменить сторонние модули, от которых зависит тестируемый компонент, если они неправильно работают в среде тестирования или вообще не нужны для тестирования

👉 фиктивные таймеры
позволяют контролировать (например, ускорять) функции, связанные со временем

#документация #тестирование
Тестирование в React: подготовка и завершение

Чтобы протестировать компонент, его нужно "отрендерить" в среде исполнения - создать элемент-контейнер для монтирования компонента, а после завершения теста удалить его. Для этих действий используются функции фреймворка Jest:

- beforeEach (создание контейнера)
- afterEach (очистка и удаление контейнера)

Для очистки контейнера можно использовать метод ReactDOM.unmountComponentAtNode.

#документация #тестирование
Утилиты для тестирования

Пакет react-dom предоставляет набор утилитарных функция для тестирования компонентов - react-dom/test-utils.

Раздел в документации (рус.): https://ru.reactjs.org/docs/test-utils.html

- act - имитация рендера компонента
- isElement(element) - проверка на React-элемент
- isElementOfType(element, componentClass) - проверка на тип элемента
- isDOMComponent(instance) - проверка на DOM-элемент
- isCompositeComponent(instance) - проверка на пользовательский компонент (классовый или функциональный, не важно)
- isCompositeComponentWithType(instance, componentClass) - проверка на тип пользовательского компонента
- findAllInRenderedTree(tree, testCallback) - поиск компонентов в дереве с помощью коллбэка (должен вернуть true или false)
- scryRenderedDOMComponentsWithClass(tree, className) - поиск DOM-элементов с указанным классом в дереве
- findRenderedDOMComponentWithClass(tree, className) - то же, что и предыдущий метод, но возвращает единственный результат или исключение
- scryRenderedComponentsWithType(tree, componentClass) - поиск компонентов с указанным классом
- findRenderedComponentWithType(tree, componentClass) - то же, что и предыдущий метод, но возвращает единственный результат или исключение
- renderIntoDocument(element)
- Simulate.{eventName}(element,[eventData]) - симулировать отправку события

#документация #тестирование
Тестирование простого компонента

Самый примитивный пример тестирования компонента:

https://codesandbox.io/s/testing-react-junior-i6ldm?file=/src/Hello.test.js

- beforeEach (метод Jest) - создание контейнера для рендера
- ReactDOM.unmountComponentAtNode - размонтирование React-компонента из контейнера после выполнения теста
- afterEach (метод Jest) - удаление контейнера после выполнения теста
- it (метод Jest) - объявление блока тестов
- act (вспомогательная функция из пакета react-dom/test-utils) - обеспечивает завершенность процесса рендеринга компонента перед выполнением тестов
- ReactDOM.render - рендерит компонент
- expect (метод Jest) - осуществляет проверку некоторого утверждения.

Jest предоставляет множество различных проверок (полный список здесь https://jestjs.io/docs/expect), мы воспользовались для примера самой простой - toBe, которая проверяет равенство примитивных значений или ссылок на объекты.

Результаты выполнения тестов можно увидеть во вкладке Tests.

#документация #примерыкода #тестирование
Mock-функции в Jest

Mock-функция - это фиктивная реализация некоторого интерфейса, предназначенная для упрощения тестирования.

Создать такую функцию можно с помощью метода jest.fn(). В качестве необязательного аргумента можно передать реализацию мок-функции (которая будет вызываться при обращении к мок-функции). Реализацию можно также установить после создания, с помощью метода mockFn.mockImplementation(fn).

Подробнее о моках в документации Jest: https://jestjs.io/docs/mock-function-api

У мок-функций есть ряд полезных методов, позволяющих, например, посмотреть все вызовы.

Кроме того, передав мок-функцию в метод expect, мы получаем еще ряд полезных утверждений вроде toHaveBeenCalled().

Помимо простых моков есть еще шпионы (spies). Они имеют интерфейс мок-функции и отслеживают вызовы некоторого существующего метода. Создать шпиона можно с помощью метода jest.spyOn(object, methodName) - он будет отслеживать вызовы object[methodName]. Для них также можно переопределить существующую функциональность с помощью mockFn.mockImplementation().

Например, можно установить шпиона для метода fetch:

jest.spyOn(global, "fetch").mockImplementation(setupFetchStub)

#тестирование #jest
Замена методов

Некоторые функции (вроде запросов данных) могут вызывать проблемы при тестировании: они долго выполняются, могут прерываться неожиданными ошибками, которые не имеют отношения к тестам, а также возвращают неопределенные данные.

Такие функции можно заменить шпионами Jest (jest.spyOn) и установить для них фиктивную реализацию.

https://codesandbox.io/s/mock-methods-react-junior-0oxgq?file=/src/Pokemon.test.js

Примечание: в примере запрос данных осуществляется с помощью метода Utils.fetch и шпион устанавливается именно для него:

jest.spyOn(Utils, 'fetch')


Это сделано для демонстрации, так как в codesandbox не получилось установить шпиона для global.fetch. В нормальной среде выполнения должно быть:

jest.spyOn(global, 'fetch')


#документация #тестирование #примерыкода
Фиктивные модули

Создать фиктивную реализацию можно не только для функции, но и для целого модуля.

Для этого предназначен метод jest.mock(moduleName, factory, options).

Пример в документации (рус.): https://ru.reactjs.org/docs/testing-recipes.html#mocking-modules

#документация #тестирование #jest
События при тестировании

Создать событие на элементе можно стандартным методом dispatchEvent, передав ему объект события.

Важно: Объекту события нужно передать опцию bubbles: true, чтобы обработчик событий React его увидел и автоматически передал в корень документа.

#документация #тестирование
Фиктивные таймеры

Jest предоставляет моки и для таймеров (setTimeout, setInterval): https://jestjs.io/ru/docs/timer-mocks.

👉 jest.useFakeTimers() и jest.useRealTimers() позволяют переключить режим работы таймеров.
👉 jest.advanceTimersByTime(ms) сдвигает время на указанное количество милисекунд.

Есть еще пара методов, которые могут пригодиться: runAllTimers и runOnlyPendingTimers.

Пример в документации (рус.): https://ru.reactjs.org/docs/testing-recipes.html#timers

#документация #тестирование #jest