Testing Library. Тестирование доступности
Если возникла необходимость затестить доступность вашего приложения, Testing Library предлагает пару полезных утилит:
getRoles
Находит на странице все элементы, имеющие роли, и возвращает их в виде объекта
logRoles
То же самое, только выводит данные в консоль.
isInaccessible
Проверяет, исключен ли элемент из дерева доступности браузера.
Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-accessibility
#тестирование #testinglibrary #документация
Если возникла необходимость затестить доступность вашего приложения, Testing Library предлагает пару полезных утилит:
getRoles
Находит на странице все элементы, имеющие роли, и возвращает их в виде объекта
logRoles
То же самое, только выводит данные в консоль.
isInaccessible
Проверяет, исключен ли элемент из дерева доступности браузера.
Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-accessibility
#тестирование #testinglibrary #документация
Testing-Library
Accessibility | Testing Library
Testing for Accessibility
👍1
Testing Library. Debugging
Библиотека предоставляет ряд возможностей для отладки
- Если запросы
- Утилита
- Метод
- Метод
- Утилита
Подробнее в документации: https://testing-library.com/docs/dom-testing-library/api-debugging
#тестирование #testinglibrary #документация
Библиотека предоставляет ряд возможностей для отладки
- Если запросы
get
или find
не находят элементов, то они выбрасывают ошибку, а в консоль выводится DOM корневого элемента (screen или container)- Утилита
prettyDOM
принимает элемент и возвращает его структуру- Метод
screen.debug()
или screen.debug(element)
также получает DOM элемента и выводит его в консоль- Метод
screen.logTestingPlaygroundURL()
выводит урл песочницы, в которой уже будет сохранен ваш документ- Утилита
logRoles
выводит все элементы, имеющие ролиПодробнее в документации: https://testing-library.com/docs/dom-testing-library/api-debugging
#тестирование #testinglibrary #документация
Testing-Library
Debugging | Testing Library
Automatic Logging
👍1
Testing Library. within и кастомные запросы
Библиотека также предоставляет ряд низкоуровневых функций для построения более точных/сложных запросов: https://testing-library.com/docs/dom-testing-library/api-custom-queries
И еще есть полезная функция
#тестирование #testinglibrary #документация
Библиотека также предоставляет ряд низкоуровневых функций для построения более точных/сложных запросов: https://testing-library.com/docs/dom-testing-library/api-custom-queries
И еще есть полезная функция
within(element)
. Она оборачивает полученный элемент и возвращает объект со всеми уже известными нам методами (как у объекта screen). И все эти методы работают "в контексте" полученного элемента: https://testing-library.com/docs/dom-testing-library/api-within#тестирование #testinglibrary #документация
Testing-Library
Custom Queries | Testing Library
DOM Testing Library exposes many of the helper functions that are used to
👍3
Forwarded from Cat in Web
Валидация форм с react-hook-form в React-приложениях
Видео (рус.): https://youtu.be/Jxfun6Jnt5Q
Быстрое и понятное введение в работу с библиотекой react-hook-form. Достаточно подробно описано, как настроить валидацию для формы с неконтролируемыми полями.
#видео #react #forms
Видео (рус.): https://youtu.be/Jxfun6Jnt5Q
Быстрое и понятное введение в работу с библиотекой react-hook-form. Достаточно подробно описано, как настроить валидацию для формы с неконтролируемыми полями.
#видео #react #forms
YouTube
Валидация форм с react-hook-form в React-приложениях
Библиотека react-hook-form упрощает создание и обработку форм в React-приложениях. Быстро, просто и эффективно.
00:00 Актуальность библиотеки форм
01:09 Создание проекта
01:41 Хук useForm
03:09 Обработчик отправки формы
04:06 Обязательное поле ввода
06:09…
00:00 Актуальность библиотеки форм
01:09 Создание проекта
01:41 Хук useForm
03:09 Обработчик отправки формы
04:06 Обязательное поле ввода
06:09…
👍3
user-event
user-event - это библиотека для симуляции пользовательских событий, которую
В чем разница между user-event и fireEvent?
fireEvent просто диспатчит события DOM, это лиш
А user-event пытается имитировать полноценное взаимодействие, которое обычно состоит из нескольких событий.
Простой пример - изменение значения поля ввода.
fireEvent просто задиспатчит событие ‘change’ с нужной строкой, хотя в реальной жизни поле сначала получит фокус, а затем будет ряд событий клавиатуры. user-event пытается все это имитировать, а заодно делает кучу проверок: например, не получится ввести текст в заблокированный инпут.
Настоящий пользователь в браузере вызывает trusted-события, которые нельзя создать программно, а user-event подменяет UI-слой, с которым взаимодействует пользователь в браузере, чтобы это имитировать.
Настройка
Прежде чем использовать методы из этой библиотеки в наших тестах, ее нужно настроить с помощью метода setup. Документация рекомендует делать это до того, как вы отрендерили DOM:
В метод можно передать ряд настроек: https://testing-library.com/docs/user-event/options
В результате
Доступные API и методы
👉 type
Для ввода значения в поле ввода.
Первым аргументом принимает само поле, вторым - текст, третим - объект с настройками.
👉 clear
Очистка editable-элемента (фокус, выделение контента, удаление)
👉 selectOptions/deselectOptions
Работа с селектами или listbox.
👉 upload
Для загрузки файлов через input[type=«file»]
👉 clipboard: copy, cut, paste
Работа с буфером обмена. Библиотека подменяет реальный window.navigator.clipboard собственным стабом.
Дока: https://testing-library.com/docs/user-event/clipboard
👉 keyboard
Для симуляции событий клавиатуры, ввода текста. Символы можно указать просто текстом, а также по значениею KeyboardEvent.key или KeyboardEvent.code
Дока: https://testing-library.com/docs/user-event/keyboard
У этого метода также есть шорткат
👉 pointer
Работа указателем. Некоторые возможности этого метода не очень актуальны, если вы работаете с jsdom, так как в этом случае реальная отрисовка страницы не происходит.
Дока: https://testing-library.com/docs/user-event/pointer
Это сложный метод, у которого есть много шорткатов (подробнее тут https://testing-library.com/docs/user-event/convenience):
⁃ click
⁃ dblClick
⁃ tripleClick
⁃ hover
⁃ unhover
#тестирование #testinglibrary #документация
user-event - это библиотека для симуляции пользовательских событий, которую
мы можем
использовать вместо fireEvent. Можно импортировать из пакета @testing-library/user-event.В чем разница между user-event и fireEvent?
fireEvent просто диспатчит события DOM, это лиш
ь удобная обе
ртка над методом dispatchEvent. А user-event пытается имитировать полноценное взаимодействие, которое обычно состоит из нескольких событий.
Простой пример - изменение значения поля ввода.
fireEvent просто задиспатчит событие ‘change’ с нужной строкой, хотя в реальной жизни поле сначала получит фокус, а затем будет ряд событий клавиатуры. user-event пытается все это имитировать, а заодно делает кучу проверок: например, не получится ввести текст в заблокированный инпут.
Настоящий пользователь в браузере вызывает trusted-события, которые нельзя создать программно, а user-event подменяет UI-слой, с которым взаимодействует пользователь в браузере, чтобы это имитировать.
Настройка
Прежде чем использовать методы из этой библиотеки в наших тестах, ее нужно настроить с помощью метода setup. Документация рекомендует делать это до того, как вы отрендерили DOM:
const user = userEvent.setup() // инициализация
render(…)
await user.click(screen.getByRole('button', {name: /click me!/i}))
В метод можно передать ряд настроек: https://testing-library.com/docs/user-event/options
В результате
мы
получаем объект user с кучей полезных методов для создания интерактивности.Доступные API и методы
👉 type
Для ввода значения в поле ввода.
Первым аргументом принимает само поле, вторым - текст, третим - объект с настройками.
user.type(input, ' World!')
👉 clear
Очистка editable-элемента (фокус, выделение контента, удаление)
user.clear(screen.getByRole('textbox'))
👉 selectOptions/deselectOptions
Работа с селектами или listbox.
user.selectOptions(screen.getByRole('listbox'), ['1', 'C'])
👉 upload
Для загрузки файлов через input[type=«file»]
user.upload(input, new File(['hello'], 'hello.png', {type: 'image/png'}))
👉 clipboard: copy, cut, paste
Работа с буфером обмена. Библиотека подменяет реальный window.navigator.clipboard собственным стабом.
Дока: https://testing-library.com/docs/user-event/clipboard
👉 keyboard
Для симуляции событий клавиатуры, ввода текста. Символы можно указать просто текстом, а также по значениею KeyboardEvent.key или KeyboardEvent.code
Дока: https://testing-library.com/docs/user-event/keyboard
У этого метода также есть шорткат
tab
(https://testing-library.com/docs/user-event/convenience) 👉 pointer
Работа указателем. Некоторые возможности этого метода не очень актуальны, если вы работаете с jsdom, так как в этом случае реальная отрисовка страницы не происходит.
Дока: https://testing-library.com/docs/user-event/pointer
Это сложный метод, у которого есть много шорткатов (подробнее тут https://testing-library.com/docs/user-event/convenience):
⁃ click
⁃ dblClick
⁃ tripleClick
⁃ hover
⁃ unhover
#тестирование #testinglibrary #документация
Testing-Library
Options | Testing Library
The following options allow to adjust the behavior of user-event APIs. They
👍2
React Testing Library. Первая проба
Наконец-то то, ради чего мы тут и собрались в общем-то. Как использовать Testing Library в React-проектах?
Очень просто, они нам предоставляют обертку @testing-library/react, которая реализует методы для удобной работы с компонентами React.
👀 Тестируем компонент Button вживую: https://codesandbox.io/p/devbox/react-testing-library-react-junior-2yqhll
Сложнее всего тут - настроить окружение, а сами тесты пишутся очень просто и логично.
Итак, нам понадобится:
👉 @testing-library/react - это обертка над @testing-library/dom, поэтому dom нам не нужен.
👉 @testing-library/jest-dom - для добавления полезных утверждений
👉 @testing-library/user-event - для эмуляции пользовательских событий
👉 а также компонент, который нужно протестировать - Button
Тесты в примере очень простые, чтобы разобраться, как это дело заводится.
Первым тестом мы проверяем, что наш компонент в принципе рендерится. Для этого используем метод render из @testing-library/react. Он возвращает объект с уже знакомыми нам методами типа getByRole, findByText и так далее. Соответственно все они работают именно с тем фрагментом, который мы отрендерили.
Находим нашу кнопку по ее тексту (можно и по роли). И утверждаем, что она есть в документе (`expect(button).toBeInTheDocument`).
Второй тест чуть сложнее, он проверяет, что при клике на кнопку вызывает функция-обработчик. Для этого мы создаем поддельную функцию (`jest.fn()`) и передаем ее в компонент в пропе
Все логично, методы удобные, ничего больше не мешает писать тесты))
#тестирование #testinglibrary #документация #примерыкода
Наконец-то то, ради чего мы тут и собрались в общем-то. Как использовать Testing Library в React-проектах?
Очень просто, они нам предоставляют обертку @testing-library/react, которая реализует методы для удобной работы с компонентами React.
👀 Тестируем компонент Button вживую: https://codesandbox.io/p/devbox/react-testing-library-react-junior-2yqhll
Сложнее всего тут - настроить окружение, а сами тесты пишутся очень просто и логично.
Итак, нам понадобится:
👉 @testing-library/react - это обертка над @testing-library/dom, поэтому dom нам не нужен.
👉 @testing-library/jest-dom - для добавления полезных утверждений
👉 @testing-library/user-event - для эмуляции пользовательских событий
👉 а также компонент, который нужно протестировать - Button
Тесты в примере очень простые, чтобы разобраться, как это дело заводится.
Первым тестом мы проверяем, что наш компонент в принципе рендерится. Для этого используем метод render из @testing-library/react. Он возвращает объект с уже знакомыми нам методами типа getByRole, findByText и так далее. Соответственно все они работают именно с тем фрагментом, который мы отрендерили.
Находим нашу кнопку по ее тексту (можно и по роли). И утверждаем, что она есть в документе (`expect(button).toBeInTheDocument`).
Второй тест чуть сложнее, он проверяет, что при клике на кнопку вызывает функция-обработчик. Для этого мы создаем поддельную функцию (`jest.fn()`) и передаем ее в компонент в пропе
onClick
. Опять рендерим, находим кнопку и кликаем на нее, используя метод click из библиотеки user-event (хотя тут можно и нативным кликом обойтись). Наконец, утверждаем, что наша мокнутая функция-обработчик была вызвана (`expect(onClick).toHaveBeenCalled()`).Все логично, методы удобные, ничего больше не мешает писать тесты))
#тестирование #testinglibrary #документация #примерыкода
👍3
React Testing Library. Пример с сетевыми запросами
Еще один пример тестирования с использованием React Testing Library: https://codesandbox.io/p/sandbox/react-testing-library-fetch-react-junior-vc465v
У нас есть компонент с кнопкой, при клике на которую из публичного api загружается и отображается картинка, то есть уходит сетевой запрос.
Нам нужно это протестировать: что при клике загружается и отображается картинка.
Алгоритм такой:
⁃ рендерим компонент
⁃ находим кнопку
⁃ кликаем
⁃ ждем, когда на странице появится картинка
⁃ проверяем ее адрес
Но для этого нам нужно перехватить и подменить запрос. Для этого существуют специальные утилиты, типа msw или nock. Они очень простые и отлично работают. Однако у меня не получилось завести их на демонстрационной площадке, поэтому пришлось использовать корявую подмену метода
В тесте стоит обратить внимание на две вещи:
👉 мы ждем, когда изображение появится на странице, используя await и find-запрос, который возвращает промис
👉 для утверждения используем метод toHaveAttribute, добавленный библиотекой @testing-library/jest-dom
#тестирование #testinglibrary #документация #примерыкода
Еще один пример тестирования с использованием React Testing Library: https://codesandbox.io/p/sandbox/react-testing-library-fetch-react-junior-vc465v
У нас есть компонент с кнопкой, при клике на которую из публичного api загружается и отображается картинка, то есть уходит сетевой запрос.
Нам нужно это протестировать: что при клике загружается и отображается картинка.
Алгоритм такой:
⁃ рендерим компонент
⁃ находим кнопку
⁃ кликаем
⁃ ждем, когда на странице появится картинка
⁃ проверяем ее адрес
Но для этого нам нужно перехватить и подменить запрос. Для этого существуют специальные утилиты, типа msw или nock. Они очень простые и отлично работают. Однако у меня не получилось завести их на демонстрационной площадке, поэтому пришлось использовать корявую подмену метода
fetch
.В тесте стоит обратить внимание на две вещи:
👉 мы ждем, когда изображение появится на странице, используя await и find-запрос, который возвращает промис
👉 для утверждения используем метод toHaveAttribute, добавленный библиотекой @testing-library/jest-dom
#тестирование #testinglibrary #документация #примерыкода
👍2
React Testing Library. Кастомный рендер
Мы можем настроить функцию
#тестирование #testinglibrary #документация #примерыкода
Мы можем настроить функцию
render
, передав вторым аргументом объект конфигурации. Например, можно добавить какую-то общую обертку с провайдерми для наших рендеров - параметр wrapper
.#тестирование #testinglibrary #документация #примерыкода
👍2
Render Hook
Помимо
Метод принимает два аргумента:
- рендер-функцию, которая должна вызвать хук и вернуть результат его работы
- объект конфигурации (опционально)
Результат работы хукуа будет доступен в поле
#тестирование #testinglibrary #документация #примерыкода
Помимо
render
у нас есть еще один метод - renderHook
. Как можно догадаться, он предназначен для работы с хуками.
import {renderHook} from '@testing-library/react'
test('returns logged in user', () => {
const {result, rerender} = renderHook((props = {}) => props, {
initialProps: {name: 'Alice'},
})
expect(result.current).toEqual({name: 'Alice'})
rerender()
expect(result.current).toEqual({name: undefined})
})
Метод принимает два аргумента:
- рендер-функцию, которая должна вызвать хук и вернуть результат его работы
- объект конфигурации (опционально)
Результат работы хукуа будет доступен в поле
result.current
.#тестирование #testinglibrary #документация #примерыкода
👍2
Руководство по React Testing Library
Статья (англ.): https://www.robinwieruch.de/react-testing-library/
Robin Wieruch по полочкам разложил, что такое RTL и как ей пользоваться.
1. RTL сама по себе не предназначена для организации тестов, поэтому нам в любом случае понадобится один тест-раннеров - Jest или Vitest. С их помощью напишем обертки тестов, а для конкретных действий уже будем использовать RTL.
2. В начале теста рендерим необходимые React-компоненты и сразу же перестаем о них думать как о React-компонентах. Мы должны тестировать обычный DOM, который видит пользователь.
3. RTL предоставляет огромное количество методов для поиска нужных элементов в отрендеренном фрагменте. Все они довольно подробно разобраны, кроме того, описано, когда и что предпочтительно использовать.
Запросы в testing-library
4. Пакет @testing-library/jest-dom добавляет множество удобных утверждений, вроде toBeInTheDocument.
5. Пользовательские события можно эмулировать с помощью fireEvent и user-event.
fireEvent
user-event
6. Разбираемся, как тестировать обработчики событий с помощью мокнутых функций.
7. И наконец учимся работать с асинхронными изменениями, например, сетевыми запросами, с помощью find-методов и waitFor. Не забываем мокать собственно методы, отправляющие запрос (axios/fetch/etc)
waitFor
пример тестирования компонента, выполняющего сетевой запрос
#ссылки #тестирование #testinglibrary
Статья (англ.): https://www.robinwieruch.de/react-testing-library/
Robin Wieruch по полочкам разложил, что такое RTL и как ей пользоваться.
1. RTL сама по себе не предназначена для организации тестов, поэтому нам в любом случае понадобится один тест-раннеров - Jest или Vitest. С их помощью напишем обертки тестов, а для конкретных действий уже будем использовать RTL.
2. В начале теста рендерим необходимые React-компоненты и сразу же перестаем о них думать как о React-компонентах. Мы должны тестировать обычный DOM, который видит пользователь.
3. RTL предоставляет огромное количество методов для поиска нужных элементов в отрендеренном фрагменте. Все они довольно подробно разобраны, кроме того, описано, когда и что предпочтительно использовать.
Запросы в testing-library
4. Пакет @testing-library/jest-dom добавляет множество удобных утверждений, вроде toBeInTheDocument.
5. Пользовательские события можно эмулировать с помощью fireEvent и user-event.
fireEvent
user-event
6. Разбираемся, как тестировать обработчики событий с помощью мокнутых функций.
7. И наконец учимся работать с асинхронными изменениями, например, сетевыми запросами, с помощью find-методов и waitFor. Не забываем мокать собственно методы, отправляющие запрос (axios/fetch/etc)
waitFor
пример тестирования компонента, выполняющего сетевой запрос
#ссылки #тестирование #testinglibrary
www.robinwieruch.de
React Testing Library Tutorial
Learn how to use React Testing Library in this tutorial. You will learn how to test your React components step by step with unit and integration tests ...
👍2
Testing Library. Полезные ресурсы
- документация Testing Library
- все существующие ARIA-роли для метода getByRole
- список утверждений, которые добавляются библиотекой @testing-library/jest-dom
#ссылки #тестирование #testinglibrary
- документация Testing Library
- все существующие ARIA-роли для метода getByRole
- список утверждений, которые добавляются библиотекой @testing-library/jest-dom
#ссылки #тестирование #testinglibrary
Testing-Library
Testing Library | Testing Library
Simple and complete testing utilities that encourage good testing practices
👍2
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React
Статья (рус.): https://reddeveloper.ru/blog/363/prostoye-rukovodstvo-po-testirovaniyu-vzaimodeistviya-s-pol-zovatelem-s-pomoshch-yu-biblioteki-testirovaniya-react
Из этой статьи мы возьмем несколько примеров того, что в принципе можно тестировать, так как у меня по-прежнему есть сомнения и вопросы в этой области.
1. Поле ввода работают правильно
Имеем селект. Тестируем, что он работает правильно и выбранное пользователем значение действительно выбирается.
Полезные методы:
- getByRole('combobox')
- userEvent.selectOptiions
- expect(select).toHaveValue
2. При вводе/выборе значения оно корректно отображается в интерфейсе
Имеем селект города. Выводим выбранный город где-то в интерфейсе Тестируем, что при выборе города в селекте он изменяется и в интерфейсе.
Полезные методы:
- поиск элемента по тексту getByText
- поиск элемента другими методами, но с уточнением его текста (в настройках метода), например,
3. При ховере появляется элемент
Имеем тултип, который должен появляться при наведении на элемент. Тестируем, что он появляется.
Полезные методы:
- userEvent.hover
- expect(tooltip).toBeInTheDocument
4. Загрузка файлов
Проверяем, что загрузчик файлов видит загруженные файлы.
Полезные методы:
- new File(['hello'], 'hello.png', {type: 'image/png'})
- userEvent.upload(filePicker, file)
- expect(filePicker.files[0]).toEqual(file)
5. Отправка формы
Имеем форму. Тестируем, что при правильном заполнении полей форма успешно отправляется.
Также тестируем все варианты валидации:
- пустые поля
- некорректно заполненные поля
Полезные методы:
- userEvent.type(field, text)
- userEvent.selectOptions(select, option)
- userEvent.click(element)
- getByPlaceholderText(text)
- getByLabelText(text)
#ссылки #тестирование #testinglibrary
Статья (рус.): https://reddeveloper.ru/blog/363/prostoye-rukovodstvo-po-testirovaniyu-vzaimodeistviya-s-pol-zovatelem-s-pomoshch-yu-biblioteki-testirovaniya-react
Из этой статьи мы возьмем несколько примеров того, что в принципе можно тестировать, так как у меня по-прежнему есть сомнения и вопросы в этой области.
1. Поле ввода работают правильно
Имеем селект. Тестируем, что он работает правильно и выбранное пользователем значение действительно выбирается.
Полезные методы:
- getByRole('combobox')
- userEvent.selectOptiions
- expect(select).toHaveValue
2. При вводе/выборе значения оно корректно отображается в интерфейсе
Имеем селект города. Выводим выбранный город где-то в интерфейсе Тестируем, что при выборе города в селекте он изменяется и в интерфейсе.
Полезные методы:
- поиск элемента по тексту getByText
- поиск элемента другими методами, но с уточнением его текста (в настройках метода), например,
getByRole('heading', { name: /moscow/i })
3. При ховере появляется элемент
Имеем тултип, который должен появляться при наведении на элемент. Тестируем, что он появляется.
Полезные методы:
- userEvent.hover
- expect(tooltip).toBeInTheDocument
4. Загрузка файлов
Проверяем, что загрузчик файлов видит загруженные файлы.
Полезные методы:
- new File(['hello'], 'hello.png', {type: 'image/png'})
- userEvent.upload(filePicker, file)
- expect(filePicker.files[0]).toEqual(file)
5. Отправка формы
Имеем форму. Тестируем, что при правильном заполнении полей форма успешно отправляется.
Также тестируем все варианты валидации:
- пустые поля
- некорректно заполненные поля
Полезные методы:
- userEvent.type(field, text)
- userEvent.selectOptions(select, option)
- userEvent.click(element)
- getByPlaceholderText(text)
- getByLabelText(text)
#ссылки #тестирование #testinglibrary
reddeveloper.ru
Простое руководство по тестированию взаимодействия с пользователем с помощью библиотеки тестирования React - RedDeveloper
В предыдущем посте я показал вам на примерах, как писать базовые тесты в React. Важнейшей частью пользовательского интерфейса приложений является взаимодействие с пользователем. При тестировании различных компонентов в React вам нужно будет имитировать взаимодействие…
👍2
Разбираемся в React Concurrency
Статья (англ.): https://www.bbss.dev/posts/react-concurrency/
Конкурентный режим в React - фича уже не новая, но наверно еще не очень широко используемая. В статье дается хороший разбор.
Основная идея в том, что React научился прерывать рендеринг. Если у нас рендериттся что-то большое и тяжелое, пользователь не может взаимодействовать со страницей. Конкурентный режим как раз должен решать эту проблему.
Как именно эта фича реализована под капотом, в статье подробно не рассказывается. Упомянуто только, что в этом задействован пакет scheduler, а также, что используется requestAnimationFrame.
В общем, "конкурентного режима" как такового у нас нет, потому что его введение многое поломает. Вместо этого нам дали несколько "конкурентных фич", которые дают возможность избирательно запустить конкуррентный рендеринг.
useTransition
Хук useTransition возвращает метод startTransition. Он запускает transition, внутри которого все изменения будут считаться прерываемыми.
Если мы изменим стейт компонента внутри такого transition, изменения начнут применяться, но могут быть прерваны, если найдется более важное дело - например, действия пользователя.
Очевидно, что этот хук нам нужен только если мы контролируем изменения (сами их запускаем). Но как быть, если у нас нет возможности обернуть изменения в transition? Например, если мы получаем некоторое изменяющееся значение через проп.
useDeferredValue
Для этого случая есть хук useDeferredValue, который оборачивает это изменяющееся значение, но об изменениях сообщает нам только тогда, когда это ничему не мешает.
***
Таким образом, новые фичи позволяют нам явно помечать изменения как "неблокирующие". Также они работают с Suspense-компонентами.
#ссылки #concurrentmode
Статья (англ.): https://www.bbss.dev/posts/react-concurrency/
Конкурентный режим в React - фича уже не новая, но наверно еще не очень широко используемая. В статье дается хороший разбор.
Основная идея в том, что React научился прерывать рендеринг. Если у нас рендериттся что-то большое и тяжелое, пользователь не может взаимодействовать со страницей. Конкурентный режим как раз должен решать эту проблему.
Как именно эта фича реализована под капотом, в статье подробно не рассказывается. Упомянуто только, что в этом задействован пакет scheduler, а также, что используется requestAnimationFrame.
В общем, "конкурентного режима" как такового у нас нет, потому что его введение многое поломает. Вместо этого нам дали несколько "конкурентных фич", которые дают возможность избирательно запустить конкуррентный рендеринг.
useTransition
Хук useTransition возвращает метод startTransition. Он запускает transition, внутри которого все изменения будут считаться прерываемыми.
Если мы изменим стейт компонента внутри такого transition, изменения начнут применяться, но могут быть прерваны, если найдется более важное дело - например, действия пользователя.
Очевидно, что этот хук нам нужен только если мы контролируем изменения (сами их запускаем). Но как быть, если у нас нет возможности обернуть изменения в transition? Например, если мы получаем некоторое изменяющееся значение через проп.
useDeferredValue
Для этого случая есть хук useDeferredValue, который оборачивает это изменяющееся значение, но об изменениях сообщает нам только тогда, когда это ничему не мешает.
***
Таким образом, новые фичи позволяют нам явно помечать изменения как "неблокирующие". Также они работают с Suspense-компонентами.
#ссылки #concurrentmode
Building Better Software Slower
Understanding React Concurrency
React v18.0 has broken ground by introducing a long-awaited feature: Concurrency! Unfortunately, despite a deluge of resources explaining how to use it, explanations of how it works are sparse. As it is a low-level feature, it’s not critical to understand…
👍5
7 бесплатных шаблонов React для разработки проектов
Статья (рус.): https://nuancesprog.ru/p/15488/
Полноценные заготовки для проектов - чтобы не начинать работу с нуля. Есть шаблоны для лендингов, портфолио, блога, админ-панели...
#ссылки #инструменты
Статья (рус.): https://nuancesprog.ru/p/15488/
Полноценные заготовки для проектов - чтобы не начинать работу с нуля. Есть шаблоны для лендингов, портфолио, блога, админ-панели...
#ссылки #инструменты
NOP::Nuances of programming
7 бесплатных шаблонов React для разработки проектов
Не обязательно начинать работу с нуля! Эти продвинутые шаблоны React помогут создавать проекты легко и быстро.
👍4👏2
useEffect vs useLayoutEffect
Статья (англ.): https://kentcdodds.com/blog/useeffect-vs-uselayouteffect
Маленькое напоминание о разнице между двумя хуками.
useEffect срабатывает асинхронно после рендера компонента - пользователь уже увидел обновленный интерфейс, и только потом исполняется ваш код.
useLayoutEffect срабатывает синхронно. Новый DOM уже подготовлен, но браузер еще не перерисовал страницу для пользователя. В этот момент исполняется ваш код.
Обычно нам нужен useEffect, так как он неблокирующий и более эффективный, а у useLayoutEffect есть вполне конкретные кейсы:
- снять метрики (позиция скролла)
- обновить DOM до перерисовки так, чтобы пользователь не увидел мерцания
- установить какие-то актуальные значения, чтобы весь последующий код из useEffect мог их использовать
#ссылки #жизненныйциклкомпонента #хуки
Статья (англ.): https://kentcdodds.com/blog/useeffect-vs-uselayouteffect
Маленькое напоминание о разнице между двумя хуками.
useEffect срабатывает асинхронно после рендера компонента - пользователь уже увидел обновленный интерфейс, и только потом исполняется ваш код.
useLayoutEffect срабатывает синхронно. Новый DOM уже подготовлен, но браузер еще не перерисовал страницу для пользователя. В этот момент исполняется ваш код.
Обычно нам нужен useEffect, так как он неблокирующий и более эффективный, а у useLayoutEffect есть вполне конкретные кейсы:
- снять метрики (позиция скролла)
- обновить DOM до перерисовки так, чтобы пользователь не увидел мерцания
- установить какие-то актуальные значения, чтобы весь последующий код из useEffect мог их использовать
#ссылки #жизненныйциклкомпонента #хуки
Kentcdodds
useEffect vs useLayoutEffect
The simple rules for when to use each.
👍5❤1
React 19 будет компилируемым
Статья (англ.): https://reacttraining.com/blog/react-19-will-be-compiled
В феврале разработчики React рассказали, что работают над компилятором (React Compiler), который добавит в React автоматическую мемоизацию и сделает жизнь разработчиков проще (пост тут).
А в статье автор разбирается, что же это значит и насколько это нововведение изменит разработку.
Зачем мы мемоизируем
Одним из важных отличий классовых компонентов от функциональных было то, что рендер был вынесен в отдельный метод класса. Там был только код рендера и больше ничего. Когда ваш компонент перерендеривался, он не дергал никакого лишнего кода, как это происходит сейчас с функциональными компонентами.
При каждом рендере у нас создаются новые функции, новые объекты, которые не равны тем, что были в предыдущем рендере, а значит, могут ненароком вызвать лишние рендеры дочерних компонентов. Чтобы избежать этого, мы используем всякие useCallback'и и useMemo - занимаемся ручной мемоизацией.
В статье приводится простой пример этого процесса.
Компилятор вроде как должен избавить нас от этой мороки, будем надеяться, что эта задумка будет удачной.
Чуть более компилируемый
Затем автор рассуждает немного о "компилируемости" React. Он приходит к заключению, что нет двух отдельных положений - либо компилируется, либо не компилируется. Компилируемость - это шкала, на которой одни инструменты (Svelte) более компилируемы, а другие менее.
Идея в том, что появление нового компилятора не должно нас пугать.
#ссылки
Статья (англ.): https://reacttraining.com/blog/react-19-will-be-compiled
В феврале разработчики React рассказали, что работают над компилятором (React Compiler), который добавит в React автоматическую мемоизацию и сделает жизнь разработчиков проще (пост тут).
А в статье автор разбирается, что же это значит и насколько это нововведение изменит разработку.
Зачем мы мемоизируем
Одним из важных отличий классовых компонентов от функциональных было то, что рендер был вынесен в отдельный метод класса. Там был только код рендера и больше ничего. Когда ваш компонент перерендеривался, он не дергал никакого лишнего кода, как это происходит сейчас с функциональными компонентами.
При каждом рендере у нас создаются новые функции, новые объекты, которые не равны тем, что были в предыдущем рендере, а значит, могут ненароком вызвать лишние рендеры дочерних компонентов. Чтобы избежать этого, мы используем всякие useCallback'и и useMemo - занимаемся ручной мемоизацией.
В статье приводится простой пример этого процесса.
Компилятор вроде как должен избавить нас от этой мороки, будем надеяться, что эта задумка будет удачной.
Чуть более компилируемый
Затем автор рассуждает немного о "компилируемости" React. Он приходит к заключению, что нет двух отдельных положений - либо компилируется, либо не компилируется. Компилируемость - это шкала, на которой одни инструменты (Svelte) более компилируемы, а другие менее.
Идея в том, что появление нового компилятора не должно нас пугать.
#ссылки
ReactTraining.com
React Will Be Compiled
React Corporate Workshops, Training, and Consulting
👍4
Использование forwardRef с generic-компонентами
Статья (англ.): https://www.totaltypescript.com/forwardref-with-generic-components
Если вы часто используете generic-компоненты (которые могут работать с разными типами данных), то возможно уже сталкивались с проблемой с пробрасыванием рефа.
Если обернуть такой компонент в функцию React.forwardRef, то внутри него перестают выводиться типы.
👉 Компонент:
👉 Использование:
Мы передаем массив чисел в проп data и ожидаем, что для параметра
👉 Решение:
Автор статьи предлагает сделать обертку над React.forwardRef, чтобы пофиксить это.
👉 Дополнительно:
- Перенаправление рефов
#ссылки #рефы
Статья (англ.): https://www.totaltypescript.com/forwardref-with-generic-components
Если вы часто используете generic-компоненты (которые могут работать с разными типами данных), то возможно уже сталкивались с проблемой с пробрасыванием рефа.
Если обернуть такой компонент в функцию React.forwardRef, то внутри него перестают выводиться типы.
👉 Компонент:
const Table = <T>(
props: {
data: T[];
renderRow: (row: T) => React.ReactNode;
},
ref: React.ForwardedRef<HTMLTableElement>
) => {
return (
<table ref={ref}>
<tbody>
{props.data.map((item, index) => (
<props.renderRow key={index} {...item} />
))}
</tbody>
</table>
);
};
const ForwardReffedTable = React.forwardRef(Table);
👉 Использование:
<ForwardReffedTable
data={[1, 2]}
renderRow={(row) => {
return <tr />;
}}
/>
Мы передаем массив чисел в проп data и ожидаем, что для параметра
row
выведется правильный тип number, но этого не происходит.👉 Решение:
Автор статьи предлагает сделать обертку над React.forwardRef, чтобы пофиксить это.
function fixedForwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
return React.forwardRef(render) as any;
}
👉 Дополнительно:
- Перенаправление рефов
#ссылки #рефы
Total TypeScript
How To Use forwardRef With Generic Components
Learn about the limitations of React's `forwardRef` TypeScript and discover a solution to enable inference on generic components.
👍2
useActionState
В репозиторий реакт пару недель назад слили PR, в котором был немного изменен хук useFormState. Честно говоря, этот хук прошел полностью мимо меня, поэтому читаю как про совершенно новый хук)
Итак, у нас был какой-то useFormState, а стал useActionState, потому что все путались и думали, что можно работать только с формами. Оказывается, можно работать с любыми асинхронными экшенами.
Основная идея хука - отслеживать состояние экшена. Он возвращает три параметра:
Action
Вызвать его можно прямо:
А можно и традиционно засунуть в форму:
isPending
Как только запустился
Таким образом мы можем отслеживать состояние экшена, работает он прямо сейчас или нет.
state
Тут вроде ничего сложного нет, раньше мы самостоятельно накручивали подобный функционал вокруг какого-то вызова функции, теперь его накрутили за нас.
Конкуррентный рендеринг
Из приятного - под капотом используются transitions, то есть конкуррентный рендеринг.
Код выше можно переписать примерно вот так:
Подводные камни
Есть и сложности, куда без них.
Так как неизвестно, используется ли isPending, он всегда устанавливается перед вызовом экшена, что приводит к дополнительному обновлению стейта (та же проблема есть и у хука useTransition).
#ссылки #хуки
В репозиторий реакт пару недель назад слили PR, в котором был немного изменен хук useFormState. Честно говоря, этот хук прошел полностью мимо меня, поэтому читаю как про совершенно новый хук)
Итак, у нас был какой-то useFormState, а стал useActionState, потому что все путались и думали, что можно работать только с формами. Оказывается, можно работать с любыми асинхронными экшенами.
Основная идея хука - отслеживать состояние экшена. Он возвращает три параметра:
const [state, action, isPending] = useActionState(someAsyncAction)
Action
action
- это запуск экшена. То есть вместо того, чтобы напрямую вызывать someAsyncAction
, мы вызываем предоставленный им action
, так как в нем есть полезные оберточки.Вызвать его можно прямо:
async function handleAction() {
await action(someData)
}
А можно и традиционно засунуть в форму:
<form action={action}>
isPending
Как только запустился
action
, параметр isPending
становится равен true. Как только action
закончит свое выполнение, isPending
станет false.Таким образом мы можем отслеживать состояние экшена, работает он прямо сейчас или нет.
state
state
- это результат работы экшена, то, что он вернул. Если вызовов было несколько, то результат работы последнего вызова.Тут вроде ничего сложного нет, раньше мы самостоятельно накручивали подобный функционал вокруг какого-то вызова функции, теперь его накрутили за нас.
Конкуррентный рендеринг
Из приятного - под капотом используются transitions, то есть конкуррентный рендеринг.
Код выше можно переписать примерно вот так:
const [state, setState] = useState(null)
const [isPending, startTransition] = useTransition()
function handleAction() {
startTransition(async () => {
const response = await someAsyncAction({ userId: 222 })
setState(response)
})
}
Подводные камни
Есть и сложности, куда без них.
Так как неизвестно, используется ли isPending, он всегда устанавливается перед вызовом экшена, что приводит к дополнительному обновлению стейта (та же проблема есть и у хука useTransition).
#ссылки #хуки
GitHub
Add `React.useActionState` by rickhanlonii · Pull Request #28491 · facebook/react
Overview
Depends on #28514
This PR adds a new React hook called useActionState to replace and improve the ReactDOM useFormState hook.
Motivation
This hook intends to fix some of the confusion and l...
Depends on #28514
This PR adds a new React hook called useActionState to replace and improve the ReactDOM useFormState hook.
Motivation
This hook intends to fix some of the confusion and l...
👍5
Как React работает под капотом. Обзор "внутренностей"
Статья (англ.): https://jser.dev/2023-07-11-overall-of-react-internals
Видео (англ.): https://youtu.be/fgxxoaeKQWo?si=2pSYXMZOoxROdh-q
Автор потратил много времени, разбираясь во внутренностях React, и наконец решил изложить все это в цикле статей. Это первая статья, в которой мы разбираемся, как работает React, на самом верхнем уровне.
Тут у нас есть 4 фазы.
1. Триггер
Мы сообщаем React, что нужно что-то отрендерить (начальный рендер) или перерендерить, потому что произошли изменения.
Грубо говоря, тут создается "таск" и отправляется в планировщик (Scheduler).
Здесь отрабатывают следующие методы:
- scheduleUpdateOnFiber()
- ensureRootIsScheduled()
- scheduleCallback()
2. Планирование
Планировщик - это очередь с приоритетами. Метод scheduleCallback добавляет новый таск в очередь, а затем он попадает в workLoop().
3. Рендер
На этом шаге вычисляется новое Fiber Tree (то, что раньше называлось Virtual DOM). Этот процесс теперь может прерываться (конкуррентный рендер).
Тут работает метод performConcurrentWorkOnRoot()
4. Коммит
И наконец вычисленные изменения вносятся в реальный DOM (comminMutationEffects()). Тут также выполняются все виды эффектов (flushPassiveEffects(), commitLayoutEffects()).
Статья хорошая, в начале автор разбирает, как именно происходит процесс дебаггинга React-приложения, и где какие методы выполняются.
#ссылки #подкапотом #fiber
Статья (англ.): https://jser.dev/2023-07-11-overall-of-react-internals
Видео (англ.): https://youtu.be/fgxxoaeKQWo?si=2pSYXMZOoxROdh-q
Автор потратил много времени, разбираясь во внутренностях React, и наконец решил изложить все это в цикле статей. Это первая статья, в которой мы разбираемся, как работает React, на самом верхнем уровне.
Тут у нас есть 4 фазы.
1. Триггер
Мы сообщаем React, что нужно что-то отрендерить (начальный рендер) или перерендерить, потому что произошли изменения.
Грубо говоря, тут создается "таск" и отправляется в планировщик (Scheduler).
Здесь отрабатывают следующие методы:
- scheduleUpdateOnFiber()
- ensureRootIsScheduled()
- scheduleCallback()
2. Планирование
Планировщик - это очередь с приоритетами. Метод scheduleCallback добавляет новый таск в очередь, а затем он попадает в workLoop().
3. Рендер
На этом шаге вычисляется новое Fiber Tree (то, что раньше называлось Virtual DOM). Этот процесс теперь может прерываться (конкуррентный рендер).
Тут работает метод performConcurrentWorkOnRoot()
4. Коммит
И наконец вычисленные изменения вносятся в реальный DOM (comminMutationEffects()). Тут также выполняются все виды эффектов (flushPassiveEffects(), commitLayoutEffects()).
Статья хорошая, в начале автор разбирает, как именно происходит процесс дебаггинга React-приложения, и где какие методы выполняются.
#ссылки #подкапотом #fiber
👍11
Немного о Fiber
Суть работы React в том, что конечный интерфейс зависит от состояния, и при изменении состояния обновляется и интерфейс. Но он обновляется не полностью с нуля, а с огромным количеством оптимизаций. React вычисляет самый оптимальный способ обновить дерево компонентов, и для этого у него есть механизм Fiber.
Раньше был другой механизм, но его полностью переписали. Новый механизм Fiber обрабатывает изменения поэтапно, и между этапами его можно прервать. Это необходимо для реализации "конкурентных" фич React.
При каждом перерендере создается новое FiberTree, начиная от самого корня приложения/измененного элемента. Можно грубо сказать, что каждый Fiber соответствует React-компоненту. А в корне дерева находится FiberRoot.
Реконсилятор сравнивает новое дерево с текущим и вычисляет оптимальный набор обновлений. После внесения изменений (фаза commit), новое дерево само станет текущим.
В ближайших постах попробуем хотя бы в общих чертах разобраться, как именно это происходит:
- как движок узнает об изменениях
- как происходит обход дерева и реконсиляция
- как Fiber позволяет реализовать конкурентный рендеринг
и всякое такое
А для начала посмотрим, что из себя представляет отдельный файбер.
🪢 Fiber
Файбер - это минимальная единица работы при реконсиляции. По сути, обработка одного файбера - это обработка изменений одного компонента. То есть файбер описывает состояние компонента.
Файбер - это простой объект с кучей полей:
-
-
-
-
-
-
-
-
🪢 FiberRoot
Кроме обычных файберов есть еще FiberRoot, который соответствует корню приложения. У него еще больше полей, среди которых самое важное для нас:
-
#подкапотом #fiber
Суть работы React в том, что конечный интерфейс зависит от состояния, и при изменении состояния обновляется и интерфейс. Но он обновляется не полностью с нуля, а с огромным количеством оптимизаций. React вычисляет самый оптимальный способ обновить дерево компонентов, и для этого у него есть механизм Fiber.
Раньше был другой механизм, но его полностью переписали. Новый механизм Fiber обрабатывает изменения поэтапно, и между этапами его можно прервать. Это необходимо для реализации "конкурентных" фич React.
При каждом перерендере создается новое FiberTree, начиная от самого корня приложения/измененного элемента. Можно грубо сказать, что каждый Fiber соответствует React-компоненту. А в корне дерева находится FiberRoot.
Реконсилятор сравнивает новое дерево с текущим и вычисляет оптимальный набор обновлений. После внесения изменений (фаза commit), новое дерево само станет текущим.
В ближайших постах попробуем хотя бы в общих чертах разобраться, как именно это происходит:
- как движок узнает об изменениях
- как происходит обход дерева и реконсиляция
- как Fiber позволяет реализовать конкурентный рендеринг
и всякое такое
А для начала посмотрим, что из себя представляет отдельный файбер.
🪢 Fiber
Файбер - это минимальная единица работы при реконсиляции. По сути, обработка одного файбера - это обработка изменений одного компонента. То есть файбер описывает состояние компонента.
Файбер - это простой объект с кучей полей:
-
tag
- тип сущности, с которой связан файбер. Таких типов 28 (функциональный компонент, классовый компонент, провайдер-контекста, suspense и пр.). Отдельно отметим HostRoot (корень host-дерева) и IndeterminateComponent. При инициализации файбера в этом поле всегда будет IndeterminateCoomponent, пока не будет определен настоящий тип.-
type
- тип самого файбера. Для функционального компонента это ссылка на конкретную функцию, для классового - на класс, для хука - указатель на хук, для HostRoot - конкретный тег и т.д.-
stateNode
- ссылка на сопоставленную DOM-ноду.-
child
, sibling
, return
- поля, обеспечивающие структуру FiberTree (наподобие связного списка). Файбер указывает на своего первого ребенка (child), на свой соседний файбер (sibling), а также на родителя, к которому нужно вернуться после выполнения компонента (return).-
memoizedProps
, pendingProps
- старые и новые пропы. При обработке они сравниваются и если изменений нет, можно сделать вывод, что компонент не изменился и не нужно тратить силы на его перерендер. -
flags
, subtreeFlags
- это битовая маска, в которой хранится информация о состоянии самого файбера и его поддерева. Информация записывается сюда после выполнения работы. Например, мы сравнили старые и новые пропы и увидели, что требуется обновить данный узел. Мы запишем сюда флаг UPDATE - узел требует перерендера. При дальнейшей обработке движок увидит этот флаг и сделает необходимые действия для перерендера.-
lanes
, childLanes
- тоже битовая маска, определяющая файбер в какой-либо лэйн (полосу). Это нужно для приоритизации задач - некоторые лэйны приоритетнее, чем другие.-
alternate
- это копия файбера, в которой и происходят изменения во время реконсиляции. В сам файбер изменения вносятся только после коммита.🪢 FiberRoot
Кроме обычных файберов есть еще FiberRoot, который соответствует корню приложения. У него еще больше полей, среди которых самое важное для нас:
-
current
- ссылка на файбер, который сейчас находится в работе. В процессе обхода дерева реконсилятором эта ссылка будет изменяться. Таким образом, корневой файбер хранит состояние всего обхода дерева.#подкапотом #fiber
👍8🔥1👏1
Первый рендеринг в React под капотом (начало)
Статья (англ.): https://jser.dev/2023-07-14-initial-mount/
Статья очень длинная, подробная, с большим количеством исходного кода React.
Создание root
Стандартное react-приложение начинается со следующего кода:
Прежде всего, вспоминаем, что react-dom - это рендер. React предоставляет весь функционал для работы с состоянием приложения, а рендеры делают обертки приложения для отрисовки в конкретной среде - в данном случае в браузере.
root - это объект ReactDOMRoot, обертка. Но под капотом у него - FiberTree. Оно представлено корневой нодой FiberRoot, а также Fiber-нодой корневого DOM-элемента (tag: HostRoot).
Триггер обновления
Когда мы вызываем метод
Внутри этой функции происходит определение приоритета обновления (запрос лэйна). Если не ошибаюсь, то для первого рендера всегда будет DefaultLane, определяющий высокий приоритет и блокирующий синхронный рендеринг.
Здесь создается объект обновления
Обновление ставится в очередь планировщика и планировщик запускается (`scheduleUpdateOnFiber`).
Таким образом, планировщик начал свой WorkLoop, и у него в очереди находится один апдейт - вставка компонента приложения в хостовый элемент.
На этом фаза триггера заканчивается.
#подкапотом #fiber #ссылки
Статья (англ.): https://jser.dev/2023-07-14-initial-mount/
Статья очень длинная, подробная, с большим количеством исходного кода React.
Создание root
Стандартное react-приложение начинается со следующего кода:
import { createRoot } from 'react-dom/client'
const $appContainer = document.getElementById('root')
const root = createRoot($appContainer)
root.render(children)
Прежде всего, вспоминаем, что react-dom - это рендер. React предоставляет весь функционал для работы с состоянием приложения, а рендеры делают обертки приложения для отрисовки в конкретной среде - в данном случае в браузере.
root - это объект ReactDOMRoot, обертка. Но под капотом у него - FiberTree. Оно представлено корневой нодой FiberRoot, а также Fiber-нодой корневого DOM-элемента (tag: HostRoot).
const hostRootFiberNode = {
// ...
tag: HostRoot,
}
const fiberRootNode = {
// ...
tag: ConcurrentRoot,
containerInfo: $appContainer,
current: hostRootFiberNode,
}
Триггер обновления
Когда мы вызываем метод
root.render(children)
, он под капотом вызывает функцию updateContainer(children, fiberRootNode)
, передавая ей корень FiberTree.Внутри этой функции происходит определение приоритета обновления (запрос лэйна). Если не ошибаюсь, то для первого рендера всегда будет DefaultLane, определяющий высокий приоритет и блокирующий синхронный рендеринг.
Здесь создается объект обновления
update
, который содержит информацию о приоритете и дочерних элементах, которые нужно вставить в хост-элемент. Обновление ставится в очередь планировщика и планировщик запускается (`scheduleUpdateOnFiber`).
Таким образом, планировщик начал свой WorkLoop, и у него в очереди находится один апдейт - вставка компонента приложения в хостовый элемент.
На этом фаза триггера заканчивается.
#подкапотом #fiber #ссылки
jser.dev
How does React do the initial mount internally?
Initial mount is the first render for a React app, it creates the internal Fiber Tree and also the DOM tree.
👍4❤1