React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Функции в списке зависимостей

Если мы указываем для хука список зависимостей (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
Снапшоты

Jest позволяет делать снапшоты - сохранять "снимки" данных. Иначе говоря, сохраняется результат рендера компонента. При внесении изменений этот "снимок" изменится.

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

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

React предоставляет пакет react-test-renderer, который может быть полезен для тестирования. Это тестовый рендерер, который умеет рендерить React-компоненты в обычные JavaScript-объекты без использования DOM или мобильного окружения - своего рода аналог снапшотов.

Подробный обзор тестового рендера в документации (рус.): https://ru.reactjs.org/docs/test-renderer.html
Руководство по тестированию с помощью снимков в документации Jest: https://jestjs.io/ru/docs/snapshot-testing

#документация #тестирование
Первый подход к документации React завершен - и завершен неплохо! Разобрались с основными понятиями, заглянули одним глазом под капот, потрогали тестирование.
Осталось еще несколько незатронутых разделов, и самый важный из них - Участие в проекте. Он сложный, не для новичков, вернемся к нему попозже.

На что нужно обратить более пристальное внимание?

На самом деле, на многое:

👉 тестирование,
👉 интеграция стилей в проект,
👉 типизация,
👉 тесное взаимодействие с DOM (например, сложные анимации),
👉 оптимизация.

Обязательно всем этим займемся. А сейчас переходим к практическому разделу документации - туториалу по созданию игры на React.
Крестики-нолики

https://ru.reactjs.org/tutorial/tutorial.html

Документация предлагает нам написать игру в крестики-нолики: игровое поле 3х3, два игрока, которые ходят по очереди. Победитель определяется по заполненности горизонтального, вертикального или диагонального ряда.

Бонусом идет еще сохранение истории ходов и возможность перейти на любой предыдущий ход и продолжить игру с него.

Игра создается по этапам, для каждого есть заготовка в codepen - можно перейти и разобраться.

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

Этап 1. Состояние игры и рендер игрового поля

https://codepen.io/gaearon/pen/QvvJOv?editors=0010

Создаются компоненты Game, Board и Square - отдельная клетка поля.

Состояние игры squares - это массив значений всех клеток (может быть null, 'x' или 'o').
По философии React состояние должно подниматься до ближайшего заинтересованного компонента, поэтому оно будет располагаться в компоненте Board и передаваться в каждую клетку Square через пропсы. Также в каждую клетку будет передаваться обработчик клика, необходимый для изменения состояния клетки при клике (устновка "крестика" или "нолика").

Здесь мы видим четкий пример однонаправленного потока данных и модели событий. Square является управляемым компонентом, поскольку полностью контролируется из Board.

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

Этап 2. Очередность хода

https://codepen.io/gaearon/pen/KmmrBy?editors=0010

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

Этап 3. Определение победителя

https://codepen.io/gaearon/pen/LyyXgK?editors=0010

Наконец, добавляется проверка заполненности рядов, которая осуществляется после каждого хода.

Этап 4. История ходов

https://codepen.io/gaearon/pen/gWWZgR?editors=0010

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

Тут есть важный момент: историй шагов будет пользоваться не только в компоненте Board, но и в списке ходов. Поэтому состояние поднимается из Board до Game.

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

https://codesandbox.io/s/tic-tac-toe-react-junior-8wb1t

Моя версия демо-игры Крестики-Нолики из документации.

Классовые компоненты заменены на функциональные + хуки. Для хранения сложного состояния используется useReducer и система событий.

Также внесены добавления, рекомендованные в документации:

👉 Отобразите позицию для каждого хода в формате (колонка, строка) в списке истории ходов. (для каждого слепка состояния в history кроме squares сохраняется coords - позиция сделанного хода).
👉 Выделите выбранный элемент в списке ходов. (Простое сравнение индексов)
👉 Перепишите Board, используя вложенные циклы для создания клеток, вместо их жёсткого кодирования.
👉 Добавьте переключатель, который позволит вам сортировать ходы по возрастанию или по убыванию. (Главное не потерять порядок ходово)
👉 Когда кто-то выигрывает, подсветите три клетки, которые привели к победе. (При расчете победителя кроме символа "Х" или "О" передается также выигрышная комбинация)
👉 Когда победителя нет, покажите сообщение о том, что игра окончилась вничью. (Проверка на наличие свободных клеток)

#документация #примерыкода
10 способов стилизации React-приложений

React не работает со стилями и не имеет предпочтений в том, как стилизовать приложение. Это значит, что мы можем действовать по своему усмотрению: можем писать инлайновый стили или по традиции подключать внешние файлы. Плюс есть еще десяток способов, которые пытаются добавить в CSS возможности JS и расширить его.

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

В статье (рус.): https://www.cat-in-web.ru/10-ways-to-style-react/ представлен краткий обзор способов стилизации React-компонентов с примерами кода.

👉 инлайновые стили
👉 внешние таблицы стилей
👉 CSS-модули - решают проблему конфликтов стилей для классов с одинаковыми именами. В разных компонентах может быть класс .container, оформленный по-разному
👉 CSS-препроцессоры
👉 Styled Components - стили пишутся как CSS, но могут использовать данные из JavaScript
👉 React JSS - стили в виде JS-объекта
👉 Radium - предоставляет обертку для компонентов и возможность удобно задавать стили, включая псевдоселекторы
👉 React Shadow - инкапсулирует стили компонента внутри теневого DOM
👉 JSX Style - набор компонентов для стилизации React приложений
👉 Утилитарные фреймворки

#инструменты #ссылки #стили
👏1