Демистификация хуков React: useCallback, useMemo и все-все-все
Вокруг React Hooks так и вьются постоянные интриги и расследования. Разберемся, что за штуки такие – эти useCallback, useMemo и прочие. Обсудить Перевод публикуется с сокращениями, автор оригинальной статьи Milu. Хуки позволяют использовать состояние и другие функции жизненного цикла при использовании функциональных компонентов вместо классов. Это более простой способ инкапсулировать stateful-поведение и побочные эффекты в UI, используя при этом меньше кода и повышая читабельность. Давайте начнем с объяснения того, что происходит при повторном рендеринге компонента, затем определим назначение useCallback и useMemo, а также когда уместно их использовать. React повторно визуализирует компоненты при каждом изменении состояния или props. Поскольку функции в JavaScript считаются объектами, созданные внутри функционального компонента React, они будут создаваться снова при каждом повторном рендеринге. Это следствие референциального равенства, которое означает, что два выглядящих совершенно одинаково объекта не являются одинаковыми, если они оба не указывают на один и тот же объект. Другими словами, когда компонент собирается повторно визуализироваться, React сравнивает каждый созданный под его исходным компонентом объект с новой версией этого объекта. И хотя объекты точно такие же, они не указывают на один и тот же объект. React идентифицирует их как разные объекты, позволяя воссоздавать снова и снова при каждом повторном рендеринге. Если useCallback и useMemo используются правильно, они необходимы, чтобы предотвратить повторные рендеры и сделать код более эффективным. Рассмотрим их структуру. Оба хука получают два параметра: функцию и массив зависимостей. useCallback возвращает один и тот же экземпляр передаваемой функции (параметр 1) вместо создания нового при каждом повторном рендеринге компонента. Новый экземпляр передаваемой функции (параметр 1) может быть создан только при изменении массива зависимостей (параметр 2). Рассмотрим пример: приложение, которое увеличивает первое значение и/или уменьшает второе, а также обновляет итоговый результат. Есть еще третье дополнительное значение, которое пользователь может обновить, однако это число не используется в вычислениях. Если пользователь взаимодействует с дополнительным значением состояния, компонент будет повторно визуализировать создание новой копии функции additionResult, даже если extraVal в ней не используется. В этом примере мы реализуем useCallback, чтобы создать новую копию функции additionResult при условии, что firstVal или secondVal будут обновлены. useMemo используется вместо того, чтобы возвращать невызванную функцию, как это делает useCallback – он работает с передаваемой функцией и возвращает результирующее значение только при изменении массива параметров. Другими словами, useMemo вызывает функцию только при необходимости и возвращает кэшированное значение для других визуализаций. В этом примере мы реализовали приложение, которое принимает число и возвращает его факториал. Например, если передать число 5, программа использовала бы рекурсивную функцию для вычисления: 5! = 5*4*3*2*1 = 120. Здесь мы использовали хук useMemo, чтобы React занимался пересчетом только при изменении числа. Если вы подумываете о добавлении useCallback и useMemo в ваш компонент, не торопитесь. Они добавляют некоторую дополнительную сложность коду и ухудшают читабельность. Менеджмент производительности с помощью useCallback и useMemo обходится дорого и это не всегда оправдано. Рассмотрите возможность использования useCallback/useMemo в следующих ситуациях: Когда компонент повторно визуализируется, он создает новые экземпляры всех объектов, включая все функции в нем: Этот хук сохраняет свое состояние между визуализациями компонентов. Магия заключается в его возможности мутировать, не вызывая повторного обновления вашего компонента, поскольку значение useRef существует вне цикла визуализации. Мы инициализируем useRef() и передаем в него начальное значение или инициализируем его пустым, а значения обновляем позже: useRef() хранит содержащий атрибут current объект, который хранит переданное значение. В нашем примере он будет содержать значение 1. Для управления фокусом, выделения текста или воспроизведения мультимедиа. Большинство элементов внутри документа имеют атрибут ref, который облегчает использование useRef для ссылки на элементы внутри HTML. В качестве примера рассмотрим тег HTML Еще одно полезное использование useRef – сохранение предыдущего значения состояния. Рассмотрим пример: есть список из трех покемонов, и вам нужно выбрать любимого. Выберите любой вариант, и вы увидите свой предыдущий выбор внизу. Это возможно в результате использования useRef: Затем каждый раз, когда мы выбираем другой вариант, он отслеживается в функции changeSelection(): Обновление значения ref считается побочным эффектом. Именно по этой причине необходимо обновить значение ref в обработчиках событий и эффектах, а не во время визуализации (если только вы не работаете с ленивой инициализацией). React docs предупреждает нас, что несоблюдение этого правила может привести к неожиданному поведению приложения. Нет. Refs – нереактивный, а значит, изменения значений не приведет к обновлению HTML. Взглянем на следующий пример. Мы инициализировали state, ref и 1000$. Этот компонент позволяет вам тратить эту сумму доллар за долларом каждый раз, когда нажимается кнопка «Spend». Когда происходит трата хранящихся в состоянии средств, запускается повторный рендеринг и обновляется view, чтобы показать вам новое значение. Теперь, если вы потратите хранящиеся в ref средства, они будут вычитаться за каждый клик, но это изменение не вызовет повторного рендеринга – вы не увидите изменений на странице. Вы можете проверить консоль, чтобы увидеть все значения ref: Нет, createRef() полезен для доступа к узлам DOM или элементам React, но он создает новый экземпляр ref на каждом рендере вместо того, чтобы сохранять значение между визуализациями при использовании в функциональных компонентах. useRef() полезен для доступа к узлам DOM или элементам React. Он сохраняет значение даже при повторной визуализации компонента. Вот пример, который позволит увидеть разницу. Мы инициализируем два значения, используя createRef и useRef. Каждый раз, когда нажимается кнопка «Add a render!», обновляется состояние renderCounter и вызывается повторная визуализация, в процессе которой проверяется, являются ли значения refs нулевыми. Если да, то присваиваем ему текущее значение состояния renderCounter. Обратите внимание, что созданное с помощью useRef значение ref равно null только при первом рендеринге, поэтому оно устанавливается в 1 единственный раз. С другой стороны, созданное с помощью createRef значение ref создается при каждом рендеринге, поэтому оно всегда null, а затем приравнивается к текущему значению состояния renderCounter. useRef() помогает создавать изменяемые переменные внутри функционального компонента, которые не будут обновляться при каждом рендеринге. React предоставляет нам поток данных, в котором родительский компонент использует props для обмена информацией со своими дочерними компонентами. Этот способ отслеживания данных отлично подходит для небольших приложений, однако по мере роста проекта вы можете обнаружить, что происходит проброс props через несколько слоев компонентов. Это называется prop drilling. При таком эффекте с использованием нескольких слоев задача обслуживания может стать очень сложной и громоздкой. Кроме того, рефакторинг кода приведет к передаче ненужных props или к использованию нескольких имен для одного props (появление бага). Альтернативой prop drilling является использование Context – простого решения, которое дает возможность доступа к данным между компонентами, даже если они не имеют отношений родитель-потомок. Context object создается с помощью API createContext() и состоит из двух элементов: провайдер и потребитель. Чтобы создать объект контекста, вы можете инициализировать его пустым или со значением: Можно также получить доступ к его элементам: Провайдер в context object должен быть обернут вокруг родительского элемента дерева компонентов – это дает каждому компоненту доступ к вашим глобальным данным. Взгляните на теги useContext() принимает объект контекста и возвращает текущие значения, доступные в качестве статических переменных. Здесь у нас есть компонент Сделать функции доступными можно и с помощью провайдера. В следующем примере создается функция updateName(), позволяющая изменять состояние имени. Если взглянуть на компонент Использующий useContext() компонент будет повторно визуализироваться при обновлении значения в context object. Вы можете столкнуться с экземпляром, где одно из значений в контексте меняется очень часто, что может привести к повторной визуализации всех использующих useContext() компонентов, даже если быстро меняющееся значение используется только в небольшом дереве компонентов. Рекомендуемое решение – разделить Context. Поэтому если у вас есть темы Light/Dark и переключатель для их выбора, необходимо создать ThemeContext и AppContext, как показано ниже: Использование context object – отличная альтернатива prop drilling. Он позволяет получить доступ к глобальным данным, не передавая их в качестве props. Reducer – это событие, которое будет выполнено, чтобы получить только одно значение. Возвращаемое значение может быть числом, строкой, массивом или даже объектом, если это оно единственное. Кроме того, reducer возвращают новое значение, а не мутируют начальное. Они очень полезны, когда необходимо получить одно значение после применения некоторой логики к группе значений. Например, если вы хотите сложить массив чисел для получения общего значения. Мы применяем метод reduce к массиву чисел reducer – функция, предоставляющая инструкции для получения одного значения. В нашем случае, для суммирования всех заданных значений в массиве nums. initialValue – начальное значение при реализации инструкций функции reducer. В нашем примере мы определяем начальное значение как 0, поэтому общее возвращаемое значение отражает только сумму значений в массиве nums. Теперь посмотрим на все это вместе. Метод reduce принимает initialValue и строит его, следуя приведенным в функции reducer инструкциям и добавляя каждое значение в массив nums до тех пор, пока не сможет вернуть одно общее значение. Хук useReducer используется для управления состоянием. В нем есть следующие параметры: reducer – предоставляющую инструкции по управлению состоянием функция, которая принимает параметры state и action и возвращает новое состояние. initialState – значение начальной точки. Оно будет меняться в соответствии с инструкциями reducer. Похоже на описанное ранее поведение функции reduce, но хук useReducer не возвращает только одно значение. Вместо этого он возвращает два элемента в виде массива: текущее состояние и функцию отправки. useReducer является предпочтительной альтернативой useState в следующих случаях: Метод reduce полезен для получения одного значения после применения некоторой логики к группе значений. Мы рассмотрели очень обширную и интересную тему, требующую большой практики и дополнительного изучения. Рекомендуем ознакомиться с официальной документацией и продолжать обучение. Удачи! Дополнительные материалы:Что происходит, когда компонент повторно визуализируется?
Для чего нужны useCallback и useMemo?
Когда их использовать?
Резюме
useRef
Почему useRef() – особенный?
Как использовать useRef()?
const testRef = useRef(1)
testRef = { current: 1 }
Для чего его использовать?
<input/>
. Создадим значение useRef и передадим его в <input/>
в качестве атрибута ref. Теперь можно изменять входной элемент с помощью нескольких функций, которые заставят <input/>
фокусироваться или размываться.
const previousSelected = useRef()
previousSelected.current = favPokemon
Где обновлять значение useRef()?
Использовать ли refs вместо state?
useRef() аналог createRef?
Резюме
useContext
Пробрасывание props-ов vsContext
Что такое context object?
const testContext = createContext();
const testContext = createContext();
Как использовать провайдер?
<Provider>
– они делают состояние name доступным для всех обернутых компонентов. Теперь компоненты <NameModifier />
и <NamePrinter />
(и любой из их дочерних элементов) имеют доступ к name состояния, даже если мы не передаем name в качестве props.
const App = () => { const { Provider } = testContext; const [name, setTestName] = useState(“Milu”); return ( <Provider value={{ name }}> <NameModifier /> <NamePrinter /> </Provider> ); };
Как получить доступ к глобальным данным с помощью useContext()?
<NamePrinter / >
, обернутый тегом Provider и мы можем получить доступ к значению name.
export const NamePrinter = () => { const { name } = useContext(testContext); return <div>My name is {name}!</div> };
Как обновить контекст?
<NameModifier />
, там обращение к функции updateName() происходит через useContext и вызывается она каждый раз, при изменении данных.Производительность
const App = ({ user, theme, themeToggle }) => { return ( <ThemeProvider value={{ theme, themeToggle }}> <AppContext value={{ user }}> <HomePage /> </AppContext> </ThemeProvider> ); };
Резюме
useReducer
Что такое reducer?
nums = [1,2,3,4,5]
. Метод принимает два параметра:
const reducer = (accumulator, currentValue) => accumulator + currentValue;
const initialValue = 0;
const reducer = (accumulator, currentValue) => accumulator + currentValue; const nums = [1,2,3,4,5]; const initialValue = 0; const totalValue = nums.reduce(reducer, initialValue);
Что такое useReducer()?
(state, action) => newState
const [state, dispatch] = useReducer(reducer, initialState);
Когда следует использовать useReducer?
Резюме
Заключение
- 6 views
- 0 Comment
Свежие комментарии