Как вызвать функцию загрузки с React useEffect только один раз

210

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

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

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}

С крючками это может выглядеть так:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}
Давид Мольнар
источник

Ответы:

397

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

function MyComponent() {
  useEffect(() => {
    loadDataOnlyOnce();
  }, []);

  return <div> {/* ... */} </div>;
}
Tholle
источник
28
В качестве альтернативы, если есть параметры, которые вы используете для извлечения данных (например, идентификатор пользователя), вы можете передать идентификатор пользователя в этом массиве, и, если он изменится, компонент будет повторно загружать данные. Многие из вариантов использования будут работать так.
Trixn
4
да ... больше о пропусках задокументировано здесь: reactjs.org/docs/…
Melounek
Это кажется самым простым ответом, но ESLint жалуется ... см. Другой ответ в этой теме stackoverflow.com/a/56767883/1550587
Саймон Хатчисон
Просто передайте loadDataOnlyOnce в массив зависимостей. Это работает?
jpmarks
88

TL; DR

useEffect(yourCallback, []) - вызовет обратный вызов только после первого рендера.

Детальное объяснение

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

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

Если вы передаете только обратный вызов - обратный вызов будет выполняться после каждого рендеринга.

При передаче второго аргумента (массива) React будет запускать обратный вызов после первого рендеринга и каждый раз, когда один из элементов в массиве изменяется. например, при размещении useEffect(() => console.log('hello'), [someVar, someOtherVar])- обратный вызов будет выполняться после первого рендера и после любого рендеринга, который один из someVarили someOtherVarбудет изменен.

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

Эдан Четрит
источник
70

хук useMountEffect

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

const useMountEffect = (fun) => useEffect(fun, [])

Используйте его в любом функциональном компоненте.

function MyComponent() {
    useMountEffect(function) // function will run only once after it has mounted. 
    return <div>...</div>;
}

О хуке useMountEffect

При использовании useEffectсо вторым аргументом массива React будет запускать обратный вызов после монтирования (начального рендеринга) и после изменения значений в массиве. Поскольку мы передаем пустой массив, он будет работать только после монтирования.

Бен Карп
источник
19
Я очень предпочитаю ваш ответ, так как правило ESLint «response-hooks / исчерпывающе-deps» всегда будет работать в пустых списках зависимостей. И, например, известный шаблон create-реагировать на приложение будет применять это правило.
Диналон
1
Полностью согласен с @Dynalon. Это должно быть принятое решение, поскольку оно не мешает правилу
ESLint
Спасибо @Dynalon и Mikado68 :-). Решение является привилегией ФП. Я был уведомлен о ваших комментариях, но ОП не было. Вы можете предложить это ему, комментируя непосредственно вопрос.
Бен Карп
2
Теперь вы можете использовать, useMountкогда вашей функции эффекта нужно что-то из реквизита, но никогда не нужно запускать снова, даже если это значение изменяется без предупреждения linter: useEffect(()=>console.log(props.val),[])будет отсутствовать предупреждение о зависимости, но useMount(()=>console.log(props.val))не вызовет предупреждение, но «работает». Я не уверен, будет ли проблема с параллельным режимом все же.
HMR
1
Мне нравится эта :) Та же самая причина, что и ранее заявленная; правило ESLint не будет жаловаться на это, плюс его имя легче понять, чем пустой массив
Frexuz
20

В качестве второго аргумента передайте пустой массив useEffect. Это эффективно говорит React, цитируя документы :

Это говорит React, что ваш эффект не зависит от каких-либо значений из реквизита или состояния, поэтому его не нужно повторно запускать.

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

function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUser(data.results[0]);
      });
  }, []); // Pass empty array to only run once on mount.
  
  return <div>
    {user ? user.name.first : 'Loading...'}
  </div>;
}

ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

Яншун Тай
источник
4

Мне нравится определять mountфункцию, она обманывает EsLint таким же образом useMount, и я нахожу это более понятным.

const mount = () => {
  console.log('mounted')
  // ...

  const unmount = () => {
    console.log('unmounted')
    // ...
  }
  return unmount
}
useEffect(mount, [])
ecoologic
источник
-2

Хитрость в том, что useEffect принимает второй параметр.

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

Или ничего не ставить

import React, { useEffect } from 'react';

function App() {
  useEffect(() => {

    // Run! Like go get some data from an API.

  }, []); //Empty array as second argument

  return (
    <div>
      {/* Do something with data. */}
    </div>
  );
}

Это обеспечит запуск useEffect только один раз.

Примечание из документов:

Если вы используете эту оптимизацию, убедитесь, что массив включает в себя все значения из области компонента (например, реквизиты и состояние), которые меняются со временем и используются эффектом. В противном случае ваш код будет ссылаться на устаревшие значения из предыдущих рендеров.

Фаррух Малик
источник
4
Если вы буквально копируете / вставляете из CSS Tricks, самое малое, что вы можете сделать, - это указать свой источник. css-tricks.com/run-useeffect-only-once
бирюзовый