Реак
Для успешного прохождения этого руководства вам понадобится свежая версия браузера Google Chrome, иначе не будут работать анимации. Выражаем благодарность Стивену Фабре за CSS для кнопки проигрывания и Джастину Виндлу за код визуализации (оригинал можно посмотреть здесь).
Как управлять состоянием React приложения без сторонних библиотек
Управление состоянием это одна из сложнейших задач при разработки приложения. Вот почему каждый день появляются все новые и новые библиотеки для управления состоянием, их становится все больше и больше, причем многие из них разрабатываются поверх уже существующих решений. В npm вы можете найти сотни «упрощенных Redux» библиотек. Однако, несмотря на то что управлять состоянием сложно, одной из причин того почему так получилось стало именно то что мы слишком переусложняем решение проблемы.
Существует метод управления состоянием который лично я пытаюсь применять еще с тех пор как я начал использовать Реакт. И теперь, после релиза хуков (hooks) и улучшения контекстов (context), этот метод управления состояниями стало очень просто использовать.
О компонентах Реакта часто говорят как о детальках Лего конструктора, из которых мы собираем наши приложения. Но эту аналогию можно применить не только к компонентам, но и к состоянию приложения. «Секрет» моего подхода к управлению состоянием в том что состояние приложения должно соответствовать структуре самого приложения.
Причиной популярности Редакса (redux), помимо прочего, стало то что react-redux решал проблему проп дриллинга (prop drilling). Редакс позволил обмениваться данными между различными частями дерева компонентов, просто передавая компонент в магическую функцию connect . Другие возможности Редакса — редюсеры, экшены и прочее, конечно хороши, но я уверен что повсеместное использование Редакса связано именно с тем, что он позволил избавиться от проп дриллинга.
Проп дриллинг это такой анти-паттерн при котором передача пропсов происходит через промежуточные компоненты которые не используют получаемые пропсы, а только передают их в следующие компоненты.
Но применение Редакса может привести к различным проблемам. Я часто вижу как разработчики переносят все состояния приложения в Редакс. Не только глобальное состояние, но и локальные. Это приводит к тому что когда вы создаете любое взаимодействие с состоянием, оно запускает взаимодействие с редюсерами, генераторами/типами экшенов и вызовами dispatch (dispatch calls). Из-за этого, просто чтобы понять как и какие стейты оказывают влияние на приложение, вам нужно открывать кучу файлов и отслеживать весь написанный там код.
Не поймите меня неправильно, это хороший подход для состояний которые действительно должны быть глобальными, но для простых состояний (таких как, например, открыто ли модальное окно или состояние строки ввода у формы) это большая проблема. Что делает ситуацию еще хуже, так это то что такой подход плохо масштабируется. Чем больше становится ваше приложение, тем хуже становится эта проблема. Конечно, вы можете подключать различные редюсеры для управления теми или иными частями приложения, но косвенная обработка всех этих генераторов экшенов и редюсеров не является оптимальной.
Держать все состояние вашего приложения в одном объекте не лучшая идея и может привести к другим проблемам (в том числе если вы не используете для этого Редакс). Когда Реакт получает новое значение, все компоненты, которые используют это значение, обновляются и запускают рендер, даже если это функциональный компонент который отвечает только за какую-то часть данных. Это может привести к проблемам с производительностью. Что я хочу сказать — у вас не будет подобных проблем если ваше состояние разделено и находится в дереве компонентов Реакта таким образом чтобы быть как можно ближе к тем местам к которым это состояние и относиться.
Тут вот какое дело — если вы создаете приложение при помощи React, у вас уже установлен пакет для управления состоянием. Чтобы использовать его, вам не нужно применять npm install или yarn add . Этот пакет не добавляет лишних байтов в ваше приложение, он уже интегрирован со всеми библиотеками для Реакта, и он уже хорошо документирован командой Реакта. Это сам Реакт.
Реакт это библиотека для управления состоянием
Когда вы создаете приложение при помощи Реакта, вы собираете множество компонентов, чтобы создать дерево компонентов. Вы начинаете с вашего и заканчиваете низкоуровневыми , и . Вы не управляете всеми низкоуровневыми составными компонентами, которые ваше приложение рендерит, из какого-то централизованного места. Вместо этого вы позволяете каждому отдельному компоненту управлять им. Как оказалось, это действительно простой и эффективный способ создания UI.
Такой же подход можно применить и к состоянию:
function Counter() < const [count, setCount] = React.useState(0) const increment = () =>setCount(c => c + 1) return > > function App() < return >
Имейте в виду что все, о чем здесь идет речь, работает и с классами. Хуки просто упрощают работу (особенно работу с контекстом, вскоре мы рассмотрим и такой вариант).
class Counter extends React.Component < state = increment = () => this.setState(() => ()) render() < return > > >
«Окей, это конечно легко — управлять одним элементом состояния в одном компоненте, но что если мне нужно разделить это состояние между компонентами? Например, что, если я хочу сделать это:
function CountDisplay() < // откуда нам брать значение для `count`? return The current counter count is > function App() < return ( ) >«Управление состоянием для подсчета значения происходит внутри , выходит, теперь мне нужна библиотека управления состоянием, чтобы получить доступ к значению count для и для его обновлений в !»
Ответ этот вопрос настолько же стар, настолько стар и сам Реакт (или старше?), и был в документации столько, сколько я себя помню: Подъём состояния
Подъём состояния (Lifting State Up) это надежный и рекомендуемый способ управления состоянием в Реакте. Вот каким образом можно применять его:
function Counter() < return > > function CountDisplay() < return The current counter count is > function App() < const [count, setCount] = React.useState(0) const increment = () =>setCount(c => c + 1) return ( ) >Теперь за управление состоянием отвечает другой компонент. При необходимости вы всегда можете поднять управление состоянием в вышестоящий компонент, вплоть до самого верхнего компонента.
«Да, конечно, но что на счет проблемы проп дриллинга (prop drilling)?»
На самом деле это проблема у которой уже довольно давно есть решение — контексты ( context ). На протяжении долгого времени люди применяли react-redux из-за предупреждений в документации Реакта об использовании контекстов. Но сейчас контексты это официально поддерживаемая часть React API, и мы можем использовать их напрямую:
// src/count/count-context.js import React from 'react' const CountContext = React.createContext() function useCount() < const context = React.useContext(CountContext) if (!context) < throw new Error(`useCount must be used within a CountProvider`) >return context > function CountProvider(props) < const [count, setCount] = React.useState(0) const value = React.useMemo(() =>[count, setCount], [count]) return /> > export // src/count/page.js import React from 'react' import from './count-context' function Counter() < const [count, setCount] = useCount() const increment = () =>setCount(c => c + 1) return > > function CountDisplay() < const [count] = useCount() return The current counter count is > function CountPage() < return ( ) >ПРИМЕЧАНИЕ. Этот код является просто примером, и я НЕ рекомендую использовать контекст для решения конкретно этой проблемы. В данном случае более простым решением стало бы просто передача состояний через пропсы (подробнее здесь: Prop Drilling). Не нужно применять контексты там где можно обойтись более простыми методами.
Одна из крутейших особенностей этого решения заключается в том что мы можем абстрагировать всю логику которую часто применяем для обновления состояния в наш useContext хук:
function useCount() < const context = React.useContext(CountContext) if (!context) < throw new Error(`useCount must be used within a CountProvider`) >const [count, setCount] = context const increment = () => setCount(c => c + 1) return < count, setCount, increment, >>
При желании можно поменять useState на useReducer :
function countReducer(state, action) < switch (action.type) < case 'INCREMENT': < return > default: < throw new Error(`Unsupported action type:
) > > > function CountProvider(props) < const [state, dispatch] = React.useReducer(countReducer, ) const value = React.useMemo(() => [state, dispatch], [state]) return /> > function useCount() < const context = React.useContext(CountContext) if (!context) < throw new Error(`useCount must be used within a CountProvider`) >const [state, dispatch] = context const increment = () => dispatch() return < state, dispatch, increment, >>
Это дает нам гибкость и уменьшает сложность кода. Вот о чем следует помнить когда вы так делаете:
- Не нужно держать все в одном объекте состояния. Разделяйте логику состояния, используйте разные контексты для разных ситуаций, например, пользовательские настройки не обязательно должны находиться в контексте в котором уже находятся уведомления.
- Не нужно делать все контексты глобальными! Держите состояние как можно ближе к месту к которому оно относится.
Подробнее о втором пункте. Структура вашего приложения может выглядеть примерно так:
function App() < return ( ) > function Notifications() < return ( ) > function UserPage() < return ( ) > function UserSettings() < // это специальный кастомный хук для AuthenticationProvider const = useAuthenticatedUser() >
Обратите внимание что у каждой страницы может быть свой собственный провайдер (provider) контекста, который передает данные необходимые компоненту находящемуся под ним. При таком подходе разделение кода (Code-Splitting) работает само по себе. То, как вы передаете данные в каждый провайдер, зависит от того как эти провайдеры используют хуки, и от того каким образом вы извлекаете данные в своем приложении. В любом случае чтобы понять как работает ваш контекст, в первую очередь вам нужно посмотреть в код компонента-провайдера.
Если хотите узнать больше о том что такое «совместное размещение», читайте статью Как сделать React приложение быстрее при помощи совместного размещения состояний и Colocation. А если интересно почитать больше о работе с контекстами, читайте статью Как эффективно применять React Context
404 — страница не найдена
Нам не удалось найти эту страницу. Попробуйте войти в систему или выберите один из подходящих результатов поиска ниже:
Нам не удалось найти эту страницу. Попробуйте сменить каталог или выберите один из подходящих результатов поиска ниже:
Значок отказа согласно Закону Калифорнии о защите конфиденциальности потребителей (CCPA)
Тема
- Светлая
- Темная
- Высокая контрастность
- Предыдущие версии
- Блог
- Участие в доработке
- Конфиденциальность
- Условия использования
- Товарные знаки
- © Microsoft 2024
Значок отказа согласно Закону Калифорнии о защите конфиденциальности потребителей (CCPA)
Тема
- Светлая
- Темная
- Высокая контрастность
- Предыдущие версии
- Блог
- Участие в доработке
- Конфиденциальность
- Условия использования
- Товарные знаки
- © Microsoft 2024
Что такое React?
React — это инструмент для создания пользовательских интерфейсов. Его главная задача — обеспечение вывода на экран того, что можно видеть на веб-страницах. React значительно облегчает создание интерфейсов благодаря разбиению каждой страницы на небольшие фрагменты. Мы называем эти фрагменты компонентами.
Вот пример разбивки страницы на компоненты:
Каждый выделенный фрагмент страницы, показанной на рисунке, считается компонентом. Но что это значит для разработчика?
Циклы компонента
Есть несколько причин по которым компонент может перерисовываться, и в зависимости от причины вызываются различные функции, позволяющие разработчику выполнять обновление определенных частей компонента.
Создание компонента
Первый цикл это создание компонента, которое обычно происходит при первом обнаружении компонента в распарсенном JSX дереве:
Компонент перерисовывается в связи с перерисовкой родительского компонента
Компонент перерисовывается в связи с внутренними изменениями (например вызов this.setState())
Компонент перерисовывается в связи с вызовом this.forceUpdate
Компонент перерисовывается в связи с перехватом ошибки
Введено в React 16 как ErrorBoundaries. Компонент может определять специальный слой, который может перехватывать ошибки и предоставлять новый метод жизненного цикла – componentDidCatch – который дает разработчику возможность обработать или залогировать эти ошибки.
@James_k_nelson — недавно опубликовал componentWillRecieveProps симулятор. ТУТ вы можете найти и поиграться с этим симулятором.
Конкурентный режим (Concurrent Mode)
Перед тем, как мы продолжим веселиться, придется сделать небольшой рефакторинг кода.
element.props.children.forEach(child => render(child, node) )
В чем проблема этого рекурсивного вызова? (Представим, что вы проходите собеседование для устройства на работу в Facebook 😉 )
. . / (@@) g/_)(_/e g/(=--=)/e //\ _| |_
Проблема в том, что после начала рендеринга, мы не остановимся, пока не отрендерим все дерево элементов целиком. Если такое дерево большое, его рендеринг может заблокировать основной поток выполнения программы (main thread) на значительное время. Если у браузера в это время появятся важные задачи, вроде обработки ввода пользователя (имеется ввиду введенных пользователем данных при заполнении полей формы, например) или плавное воспроизведение анимации, он не сможет этого сделать до завершения рендеринга.
let nextUnitOfWork = null function workLoop(deadline) < while (nextUnitOfWork && deadline.timeRemaining() >0) < nextUnitOfWork = performUnitOfWork(nextUnitOfWork) >requestIdleCallback(workLoop) > requestIdleCallback(workLoop)
Поэтому нам необходимо разделить процесс рендеринга на части. После выполнения каждой части мы позволяет браузеру выполнять свои задачи (при наличии таковых).
Мы используем requestIdleCallback для создания бесконечного цикла. requestIdleCallback похож на setTimeout , но вместо того, чтобы выполнять задачу через определенное время, браузер запускает функцию обратного вызова (в данном случае workLoop ), когда основной поток свободен от выполнения других задач (период простоя или режим ожидания браузера — отсюда idle ).
В React больше не используется requestIdleCallback . Теперь там применяется библиотека scheduler . По всей видимости, это объясняется тем, что requestIdleCallback является экспериментальной технологией и поддерживается не всеми браузерами. В частности, Safari поддерживает requestIdleCallback только в экспериментальном режиме.
Подстраховаться на случай отсутствия поддержки requestIdleCallback можно так:
window.requestIdleCallback = window.requestIdleCallback || function (handler) < const start = Date.now() return setTimeout(() => < handler(< didTimeout: false, timeRemaining: () =>Math.max(0, 50 - (Date.now() - start)) >) >, 1) >
Подробнее о requestIdleCallback можно почитать здесь и здесь.
function performUnitOfWork(nextUnitOfWork) < // TODO >
Для того, чтобы начать использовать цикл, нам нужно определить первую единицу работы. Для этого нам потребуется еще одна функция — performUnitOfWork , которая не только выполняет текущую единицу работы, но и возвращает следующую.
Какие задачи решают
Давайте рассмотрим полезность на простом примере
Что же нам даст использование этого компонента на деле
const AnotherCustomComponent: React.FC = () => < const data = ['text', 'text', 'text']; return ( onClick=* */> /> ) >
в примере выше CustomComponent будет ожидать в onClick функцию (element: string) => void так как в data был передан массив строк
Теперь давайте рассмотрим пример, где в качестве data у нас будет не массив примитивов, а массив сущностей
class User < constructor( public name: string, public lastName: string )<>get fullName() < return `$` > > const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( * в он клик нам не придется ручками подставлять тип. Как в примере выше, он сам выведется из того, что было передано в data */> onClick= alert(user.fullName)> /> ) >
теперь функция onClick будет иметь тип (element: User) => void
Давайте рассмотрим немного другой пример
Так как мы в data передавали массив юзеров children будет иметь тип (element: User) => React.ReactElement
По аналогии с дженериками-функциями в дженерики-компоненты можно явно определить нужный тип, с которым мы бы хотели работать, но такой синтаксис я редко использую
Пример валидного кода:
const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( * тут мы явно задаем что мы хотим работать с юзером */> data= onClick= alert(user)> > < // тут тип как в onClick тоже высчитается (user) =>< return Пользователь: > > ) >Пример не валидного кода:
const AnotherCustomComponent: React.FC = () => < const data = [ new User('Джон', 'Сина'), new User('Дуэйн', 'Джонсон'), new User('Дейв', 'Батиста'), ]; return ( * тут мы явно задаем что мы хотим работать со строкой и при передаче в data массив юзеров будет ошибка */> data= onClick= alert(user)> > < (user) =>< return Пользователь: > > ) >Пользовательский хук – создай мир своими руками
Пользовательские хуки – это те же самые функции, которые внутри себя используют какие-либо из стандартных хуков. Единственное требование, которое здесь необходимо соблюдать – относиться к ним, как к хукам. То есть, соблюдать правила, что мы используем при работе с хуками: не вызывать их внутри условных конструкций (таких, как if или switch) и внутри циклов (например for), а также не использовать хуки внутри колбэков других хуков.
Для того чтобы все в команде соблюдали указанные правила и понимали, что это хуки, а не просто методы, называть их лучше в формате useИмяХука.
Рассмотрим пример пользовательского хука:
const useSingleLog = () => < useEffect(() =>< console.log("I am single log"); >, []); >;
Как вы видите, мы создали хук, который позволяет нам единожды вывести строку в консоль. Он содержит в себе хук useEffect. По сути, мы можем использовать его в любых компонентах.
Спасибо за внимание! Надеемся, что материал был вам полезен.
P.S. Если у вас есть базовые знания Frontend и вы хотите их углубить, приглашаем зарегистрироваться на наш онлайн-практикум (до 28 февраля). Также 24 февраля проведем вебинар для всех желающих.
Спасибо за внимание!
Авторские материалы для frontend-разработчиков мы также публикуем в наших соцсетях – ВКонтакте и Telegram.
Источники:
https://habr.com/ru/articles/507572/&rut=9f2aad97fc122971ec74ec93a13e5a5f40105f16779667fca3c6f168b12646a9
https://learn.microsoft.com/ru-ru/windows/dev-environment/javascript/react-on-windows&rut=d953b678697a061e6ac3e74847938fa138131b4612f2dc45cbf8366e1a566a86
https://habr.com/ru/companies/ruvds/articles/343022/&rut=7fce04072753d182504f1c00476897ccab494c293ac1f8c4024ef08d1e1c869e
https://habr.com/ru/articles/358090/&rut=8f879f66b05aa242e8eadc336b7c2f646c95731f0f9bf751602000993764d1e7
https://habr.com/ru/companies/timeweb/articles/586972/&rut=b10a4de06e3595f08c74207cb3572659995ac49e33f1c6d55c35ebf446970a37
https://habr.com/ru/articles/576646/&rut=f6cdc9c4ec50d908bf6c30643754ce841df2f942fd6d72a09749b939c2ae5ee8
https://habr.com/ru/companies/simbirsoft/articles/652321/&rut=cb0990736f94680ab8388490d71b7fa41b2db0090af7896e027d6ff78668ed3e