Как работают хуки useEffect

JavaScript React.js

Автор: Дэйв Седдиа Переводчик: Front-end Xiaozhi Источник: daveceddia.

Ставь лайк и смотри, поиск в WeChat【Переезд в мир】Обратите внимание на этого человека, который не имеет большого фабричного прошлого, но имеет восходящий и позитивный настрой. эта статьяGitHub GitHub.com/QQ449245884…Он был включен, статьи были классифицированы, и многие мои документы и учебные материалы были систематизированы.

Все говорили, что нет проекта для написания резюме, поэтому я помог вам найти проект, и это было с бонусом.【Учебник по строительству】.

Представьте себе: у вас есть функциональный компонент, который отлично работает, и однажды нам нужно добавить к нему метод жизненного цикла.

Хорошо...

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

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

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function LifecycleDemo() {
  useEffect(() => {
    // 默认情况下,每次渲染后都会调用该函数
    console.log('render!');

    // 如果要实现 componentWillUnmount,
    // 在末尾处返回一个函数
    // React 在该函数组件卸载前调用该方法
    // 其命名为 cleanup 是为了表明此函数的目的,
    // 但其实也可以返回一个箭头函数或者给起一个别的名字。
    return function cleanup () {
        console.log('unmounting...');
    }
  })  
  return "I'm a lifecycle demo";
}

function App() {
  // 建立一个状态,为了方便
  // 触发重新渲染的方法。
  const [random, setRandom] = useState(Math.random());

  // 建立一个状态来切换 LifecycleDemo 的显示和隐藏
  const [mounted, setMounted] = useState(true);

  // 这个函数改变 random,并触发重新渲染
  // 在控制台会看到 render 被打印
  const reRender = () => setRandom(Math.random());

  // 该函数将卸载并重新挂载 LifecycleDemo
  // 在控制台可以看到  unmounting 被打印
  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'));

существуетCodeSandboxпопробуй.

Нажмите "Show/Hide"кнопка, посмотри в консоль, она печатает перед тем как исчезнуть"unmounting...", и когда он снова появится, напечатайте "render!".

Теперь нажмитеRe-renderкнопка. Каждый раз, когда вы нажимаете, он попадаетrender!, который также печатаетumounting, что кажется странным.

Почему каждый рендер печатает 'unmounting'.

Мы можем выборочно выбрать изuseEffectвернутьcleanupФункция вызывается только тогда, когда компонент размонтирован. React очистится, когда компонент будет размонтирован. Как стало известно ранее,effectОн выполняется каждый раз при рендеринге. Вот почему React реагирует на предыдущий эффект перед выполнением текущего эффекта.effectочистить. Это на самом деле лучше, чемcomponentWillUnmountЖизненный цикл более мощный, потому что он позволяет нам выполнять побочные эффекты до и после каждого рендеринга, если это необходимо.

неполный жизненный цикл

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

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

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

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

const [value, setValue] = useState('initial');

useEffect(() => {
  // 仅在 value 更改时更新
  console.log(value);
}, [value]) 

В приведенном выше примере мы передаем[value]как второй параметр. Что делает этот параметр? еслиvalueЗначение5и когда наш компонент повторно рендерируетvalueПо-прежнему равно 5, React будет[5]и следующий рендеринг[5]Сравнивать. потому что все элементы в массиве равны(5 === 5), React пропустит этоeffect, что оптимизирует производительность.

Выполняется только при монтировании и размонтировании

Если вы хотите выполнить только один запускeffect(выполняется только при монтировании и размонтировании компонента), можно передать пустой массив ([]) в качестве второго параметра. Это говорит React, что вашeffectЭто не зависит отpropsилиstateлюбое значение в , поэтому его никогда не нужно повторять. Это не особый случай — он по-прежнему следует тому, как работают зависимые массивы.

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

Это будет напечатано только при первом отображении компонента.mounted, который печатается после удаления компонента:unmounting.

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

Выполняется только во время монтирования

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

import React, { useEffect, useState, useRef } from "react";
import ReactDOM from "react-dom";

function App() {
  // 存储对 input 的DOM节点的引用
  const inputRef = useRef();

  // 将输入值存储在状态中
  const [value, setValue] = useState("");

  useEffect(
    () => {
      // 这在第一次渲染之后运行
      console.log("render");
      // inputRef.current.focus();
    },
	// effect 依赖  inputRef
    [inputRef]
  );

  return (
    <input
      ref={inputRef}
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));

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

Поэтому, даже если мы будем[inputRef]так какuseEffectВторой аргумент передается, на самом деле он запускается только один раз при начальном монтировании. Это в основномcomponentDidMountэффект.

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

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

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

import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";

function Reddit() {
  const [posts, setPosts] = useState([]);

  useEffect(async () => {
    const res = await fetch(
      "https://www.reddit.com/r/reactjs.json"
    );

    const json = await res.json();

    setPosts(json.data.children.map(c => c.data));
  }); // 这里没有传入第二个参数,你猜猜会发生什么?

  // Render as usual
  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вернутьsetter, поэтому он не будет воссоздаваться при каждом рендеринге, поэтомуeffectбудет работать только один раз.

Обновлять при изменении данных

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

Сначала изменимRedditкомпоненты для принятияsubredditв качестве реквизита и на основеsubredditПолучить данные только тогда, когдаpropПовторить при измененииeffect.

// 从props中解构`subreddit`:
function Reddit({ subreddit }) {
  const [posts, setPosts] = useState([]);

  useEffect(async () => {
    const res = await fetch(
      `https://www.reddit.com/r/${subreddit}.json`
    );

    const json = await res.json();
    setPosts(json.data.children.map(c => c.data));

    // 当`subreddit`改变时重新运行useEffect:
  }, [subreddit, setPosts]);

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

ReactDOM.render(
  <Reddit subreddit='reactjs' />,
  document.querySelector("#root")
);

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

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

  // Update the subreddit when the user presses enter
  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"));

Попробуйте этот пример в CodeSandbox.

Приложение поддерживает здесь два состояния: текущее входное значение и текущееsubreddit. форма отправки будет отправленаsubreddit, что приводит кRedditВосстановить данные.

Кстати: Будьте внимательны при вводе, так как нет обработки ошибок, поэтому при вводеsubredditЕсли нет, приложение взорвется, и в качестве упражнения для вас будет реализована обработка ошибок.

Вы можете использовать только одно состояние для сохранения ввода, а затем отправить то же значение вReddit,ноRedditКомпонент извлекает данные каждый раз, когда нажимается клавиша.

верхuseStateВыглядит немного странно, особенно вторая строчка:

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

мы кладемreactjsНачальное значение передается в первое состояние, что имеет смысл, это значение никогда не изменится.

А как насчет второй строки, что, если начальное состояние изменилось, например, когда вы вводитеboxкогда.

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

много применений

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

а такжеcomponentDidMount,componentDidUpdateРазница в том, что после того, как браузер завершит компоновку и рисунок, он передается вuseEffectФункция будет вызываться лениво. Это делает его подходящим для многих распространенных сценариев побочных эффектов, таких как настройка подписок и обработка событий, поэтому операции, которые блокируют обновление экрана браузером, не должны выполняться в functions.

Тем не менее, не всеeffectможно задержать. Например, видимые пользователю изменения DOM должны выполняться синхронно до того, как браузер выполнит следующую отрисовку, чтобы пользователь не заметил визуального несоответствия. (Концептуально похоже на разницу между пассивным прослушиванием событий и активным прослушиванием событий.)ReactВдобавокuseLayoutEffectКрюк для обработки такого родаeffect. это иuseEffectСтруктура та же, разница только во времени вызова.

несмотря на то чтоuseEffectЗадержит выполнение после того, как браузер отрисует, но гарантированно выполнится до любых новых рендеров. React обновит последний рендер перед обновлением компонента.effect.

оригинал:Дэйв измерил эффект wai.com/use — а также…

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

общаться с

Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.