Как работает хук UseEffect()

useEffect hook дает вам лучший способ подумать о жизненном цикле вашего компонента.

С useEffect, вы можете обрабатывать события жизненного цикла непосредственно внутри функциональных компонентов. А именно три из них: componentDidMount, componentDidUpdateа также componentWillUnmount. Все с одной функцией! Сумасшедший, я знаю. Давайте посмотрим пример.

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom'; function LifecycleDemo() { useEffect(() => { console.log('render!'); return () => console.log('unmounting...'); }) return "I'm a lifecycle demo";
} function App() { const [random, setRandom] = useState(Math.random()); const [mounted, setMounted] = useState(true); const reRender = () => setRandom(Math.random()); const toggle = () => setMounted(!mounted); return ( <> <button onClick={reRender}>Re-render</button> <button onClick={toggle}>Show/Hide LifecycleDemo</button> {mounted && <LifecycleDemo/>} </> );
} ReactDOM.render(<App/>, document.querySelector('#root'));

Нажмите кнопку Показать/Скрыть. Посмотрите на консоль. Он печатает «размонтирование» перед тем, как исчезнуть, и «рендеринг!» когда он снова появится.

Консольный вывод нажатия показать/скрыть

Теперь попробуйте кнопку Re-render. С каждым щелчком он печатает «рендеринг!» а также он печатает «размонтирование». Это кажется странным…

Консольный вывод нажатия Re-render

Почему он «размонтируется» при каждом рендере?

Что ж, функция очистки, которую вы можете (опционально) вернуть из useEffect не Только вызывается при размонтировании компонента. Он вызывается каждый раз перед запуском этого эффекта — для очистки от последнего запуска. Это на самом деле мощнее, чем componentWillUnmount lifecycle, потому что он позволяет запускать побочный эффект до и после каждого рендеринга, если вам это нужно.

Не совсем жизненные циклы

useEffect запускается после каждого рендеринга (по умолчанию) и может дополнительно очищаться перед повторным запуском.

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

Предотвратить использование useEffect при каждом рендеринге

Если вы хотите, чтобы ваши эффекты запускались реже, вы можете указать второй аргумент — массив значений. Думайте о них как о зависимости для этого эффекта. Если одна из зависимостей изменилась с момента последнего раза, эффект запустится снова. (Он также будет работать после первоначального рендеринга)

const [value, setValue] = useState('initial'); useEffect(() => { console.log(value);
}, [value])

Другой способ думать об этом массиве: он должен содержать все переменные, которые функция эффекта использует из окружающей области. Так что, если он использует опору? Это входит в массив. Если он использует кусок состояния? Это входит в массив.

Запуск только при монтировании и размонтировании

Вы можете передать специальное значение пустой массив [] как способ сказать «только запускать на монтирование и размонтировать». Итак, если мы изменили наш компонент выше, чтобы вызвать useEffect как это:

useEffect(() => { console.log('mounted'); return () => console.log('unmounting...');
}, [])

Затем он напечатает «mounted» после первоначального рендеринга, будет молчать в течение всей своей жизни и на выходе напечатает «unmounting…».

Однако это сопровождается большим предупреждением: передача пустого массива чревата ошибками. Легко забыть добавить к нему элемент, если вы добавите зависимость, и если вы скучать зависимость, то это значение будет устаревшим в следующий раз useEffect работает, и это может вызвать некоторые странные проблемы.

Сосредоточьтесь на горе

Иногда вы просто хотите сделать одна маленькая вещь во время монтирования, и для этого требуется переписать функцию как класс.

В этом примере давайте посмотрим, как вы можете сфокусировать элемент управления вводом при первом рендеринге, используя useEffect в сочетании с useRef крюк.

import React, { useEffect, useState, useRef } from "react";
import ReactDOM from "react-dom"; function App() { const inputRef = useRef(); const [value, setValue] = useState(""); useEffect( () => { console.log("render"); }, [inputRef] ); return ( <input ref={inputRef} value={value} onChange={e => setValue(e.target.value)} /> );
} ReactDOM.render(<App />, document.querySelector("#root"));

Вверху мы создаем пустую ссылку с useRef. Передача его на вход ref prop позаботится о его настройке после рендеринга DOM. И, что важно, значение, возвращаемое useRef будет стабильным между рендерами — он не изменится.

Итак, хотя мы проходим мимо [inputRef] в качестве 2-го аргумента useEffect, он будет эффективно запускаться только один раз при начальном монтировании. Это в основном «componentDidMount» (за исключением времени, о котором мы поговорим позже).

Получить данные с помощью useEffect

Давайте рассмотрим еще один распространенный вариант использования: получение данных и их отображение. В компоненте класса вы должны поместить этот код в componentDidMount метод. Чтобы сделать это с крючками, мы будем тянуть в useEffect. Нам также понадобится useState для хранения данных.

Стоит отметить, что когда часть новой функции Suspense React, извлекающая данные, будет готова, это будет предпочтительный способ извлечения данных. Получение из useEffect есть одна большая проблема (которую мы рассмотрим), и Suspense API будет намного проще в использовании.

Вот компонент, который извлекает сообщения из Reddit и отображает их:

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom"; function Reddit() { const [posts, setPosts] = useState([]); useEffect(() => { async function fetchData() { const res = await fetch( " ); const json = await res.json(); setPosts(json.data.children.map(c => c.data)); } fetchData(); }); return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> );
} ReactDOM.render( <Reddit />, document.querySelector("#root")
);

Вы заметите, что мы не передаем второй аргумент в useEffect здесь. Это плохо. Не делай этого.

Отсутствие второго аргумента приводит к useEffect для запуска каждого рендера. Затем, когда он запускается, он извлекает данные и обновляет состояние. Затем, когда состояние обновляется, компонент перерисовывается, что запускает useEffect опять таки. Вы можете видеть проблему.

Чтобы исправить это, нам нужно передать массив в качестве второго аргумента. Что должно быть в массиве?

Давай, подумай об этом на секунду.

Единственная переменная, которая useEffect зависит от setPosts. Поэтому мы должны передать массив [setPosts] здесь. Потому что setPosts является сеттером, возвращаемым useStateон не будет воссоздаваться при каждом рендеринге, поэтому эффект будет выполняться только один раз.

Забавный факт: когда вы звоните useState, возвращаемая им функция установки создается только один раз! Каждый раз при рендеринге компонента будет один и тот же экземпляр функции, поэтому эффект может безопасно зависеть от одного экземпляра. Этот забавный факт относится и к dispatch функция, возвращаемая useReducer.

Повторная выборка при изменении данных

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

Сначала мы изменим Reddit компонент для принятия субреддита в качестве реквизита, получения данных на основе этого субреддита и повторного запуска эффекта только при изменении реквизита:

function Reddit({ subreddit }) { const [posts, setPosts] = useState([]); useEffect(() => { async function fetchData() { const res = await fetch( ` ); const json = await res.json(); setPosts(json.data.children.map(c => c.data)); } fetchData(); }, [subreddit, setPosts]); return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> );
} ReactDOM.render( <Reddit subreddit="reactjs" />, document.querySelector("#root")
);

Это по-прежнему жестко закодировано, но теперь мы можем настроить его, обернув Reddit компонент с тем, который позволяет нам изменить субреддит. Добавьте этот новый компонент приложения и визуализируйте его внизу:

function App() { const [inputValue, setValue] = useState("reactjs"); const [subreddit, setSubreddit] = useState(inputValue); const handleSubmit = e => { e.preventDefault(); setSubreddit(inputValue); }; return ( <> <form onSubmit={handleSubmit}> <input value={inputValue} onChange={e => setValue(e.target.value)} /> </form> <Reddit subreddit={subreddit} /> </> );
} ReactDOM.render(<App />, document.querySelector("#root"));

Приложение хранит здесь 2 части состояния — текущее входное значение и текущий субреддит. Отправка ввода «зафиксирует» субреддит, что вызовет Reddit для повторного извлечения данных из нового выбора. Обертывание ввода в форму позволяет пользователю нажать Enter для отправки.

Кстати: печатайте внимательно. Там нет обработки ошибок. Если вы введете несуществующий сабреддит, приложение взорвется. Однако реализация обработки ошибок была бы отличным упражнением! 😉

Здесь мы могли бы использовать только 1 часть состояния — для хранения входных данных и отправки того же значения в Reddit — но тогда Reddit компонент будет извлекать данные при каждом нажатии клавиши.

useState вверху может выглядеть немного странно, особенно вторая строка:

const [inputValue, setValue] = useState("reactjs");
const [subreddit, setSubreddit] = useState(inputValue);

Мы передаем начальное значение «reactjs» в первую часть состояния, и это имеет смысл. Это значение никогда не изменится.

Но как насчет второй строки? Что, если начальное состояние изменения? (и это будет, когда вы наберете в поле)

Помните, что useState имеет состояние (подробнее о useState). Он использует только начальное состояние однажды, при первом рендеринге. После этого игнорируется. Так что можно безопасно передавать переходное значение, например свойство, которое может измениться, или какую-то другую переменную.

Сто и одно использование

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

Одно дело нет хорошо для внесения изменений DOM, которые видны пользователю. Как работает тайминг, функция эффекта сработает только после браузер закончил с макетом и отрисовкой — слишком поздно, если вы хотели внести визуальные изменения.

Для этих случаев React предоставляет useMutationEffect а также useLayoutEffect крючки, которые работают так же, как useEffect кроме случаев, когда они уволены. Взгляните на документы для использованияЭффект и особенно раздел о время эффектов если вам нужно сделать видимые изменения DOM.

Это может показаться дополнительным усложнением. Еще одна вещь, о которой нужно беспокоиться. Это как-то так, к сожалению. Положительный побочный эффект этого (хех) заключается в том, что, поскольку useEffect запускается после макета и рисования, медленный эффект не сделает пользовательский интерфейс дерганым. Недостатком является то, что если вы перемещаете старый код из жизненных циклов в хуки, вы должны быть немного осторожны, так как это означает useEffect почти, но не совсем эквивалентен componentDidUpdate что касается сроков.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *