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

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

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

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

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

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}

person Dávid Molnár    schedule 02.11.2018    source источник


Ответы (7)


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

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

  return <div> {/* ... */} </div>;
}
person Tholle    schedule 02.11.2018
comment
В качестве альтернативы, если есть параметры, которые вы используете для получения данных (например, идентификатор пользователя), вы можете передать идентификатор пользователя в этом массиве, и если он изменится, компонент будет повторно получать данные. Многие варианты использования будут работать именно так. - person trixn; 02.11.2018
comment
да ... подробнее о пропуске задокументировано здесь: responsejs.org/docs/ - person Melounek; 02.11.2018
comment
Это кажется самым простым ответом, но ESLint жалуется ... см. Другой ответ в этой теме stackoverflow.com/a/56767883/1550587 - person Simon Hutchison; 02.05.2020
comment
Просто передайте loadDataOnlyOnce в массив зависимостей. Это работает? - person jpmarks; 03.07.2020
comment
Нет, потому что при изменении loadDataOnlyOnce (не в этом примере, но lint все равно не будет жаловаться на нелокальные переменные), он повторно запустит эффект. Решением было бы либо создать отдельную функцию для крючка, как в другом ответе здесь (эффективно обмануть ESLint), либо иметь useRef с логическим значением, которое вы установили после первого запуска, и не запускать снова, если оно установлено. - person riv; 01.12.2020
comment
Что не так с useEffect(loadDataOnlyOnce, []);? - person Software Engineer; 01.01.2021
comment
да, это отлично работает - person NovemberSpawn; 08.06.2021

TL; DR

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

Подробное объяснение

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

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

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

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

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

person Edan Chetrit    schedule 02.04.2019

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 выполнит обратный вызов после монтирования (начального рендеринга) и после изменения значений в массиве. Поскольку мы передаем пустой массив, он запустится только после монтирования.

person Ben Carp    schedule 26.06.2019
comment
Я очень предпочитаю ваш ответ, так как правило ESLint react-hooks / excustive-deps всегда будет терпеть неудачу в пустых списках зависимостей. И, например, знаменитый шаблон приложения create-response-app будет применять это правило. - person Dynalon; 24.07.2019
comment
Полностью согласен с @Dynalon. Это должно быть приемлемое решение, поскольку оно не противоречит правилу ESLint. - person Mikado68; 12.08.2019
comment
Спасибо @Dynalon и Mikado68 :-). Решение - привилегия ОП. Меня уведомили о ваших комментариях, а вот OP - нет. Вы можете предложить ему это, прокомментировав непосредственно вопрос. - person Ben Carp; 12.08.2019
comment
@ Mikado68 см. Мой комментарий выше - person Ben Carp; 12.08.2019
comment
Теперь вы можете использовать useMount, когда вашей функции эффекта требуется что-то от реквизита, но никогда не нужно запускать снова, даже если это значение изменится без предупреждения линтера: useEffect(()=>console.log(props.val),[]) будет отсутствовать предупреждение о зависимости, но useMount(()=>console.log(props.val)) не вызовет предупреждения, но работает. Я не уверен, что возникнет проблема с параллельным режимом. - person HMR; 08.10.2019
comment
Если мне чего-то не хватает, ESLint все еще жалуется внутри настраиваемого хука useMountEffect. Я выключил его с помощью eslint-disable-line, а лучше всего один раз в новом файле ловушек, но все равно немного раздражает. Похоже, это должно быть разрешено правилом. - person dramus; 06.01.2020
comment
@ DávidMolnár, см. Комментарии Dynalon и Mikado68 выше. - person Ben Carp; 30.01.2020
comment
Мне нравится этот :) Причина та же, что и ранее; правило ESLint не будет жаловаться на это, плюс его название легче понять, чем пустой массив - person Frexuz; 31.03.2020
comment
Это решение, которое я искал, спасибо @BenCarp - person Sarang PM; 15.07.2020
comment
Я не совсем понимаю ... "react-hooks/exhaustive-deps" все еще жалуется на пустой массив в const useMountEffect = (fun) => useEffect(fun, []) - person Vincent Chan; 04.09.2020
comment
@VincentChan, он жалуется на пустой массив, а скулит только на. Он не скулит, когда вы вызываете useMountEffect. Очевидно, вы можете проигнорировать это, и вам нужно проигнорировать только один раз. Вы скрываете этот шум / беспорядок от своих компонентов - person Ben Carp; 04.09.2020
comment
Спасибо! Хотя я думаю, что это указывает на недостаток в "react-hooks/exhaustive-deps", особенно потому, что это канонический способ запускать вещи на монтировании. Это решение функционально перемещает проблему из компонента в другое место вместо того, чтобы принципиально решать проблему с пустыми зависимостями. - person Vincent Chan; 08.09.2020
comment
А как насчет того, чтобы вызвать функцию с аргументами? - person Brijesh Prasad; 22.10.2020
comment
Здесь есть многочисленные комментарии о недостатках в правилах ESLint для react-hooks/exhaustive-deps, и я здесь пытаюсь найти решение проблемы, аналогичной проблеме OP. Но я не уверен, что здесь действительно ошибаются правила ESLint. Это правило линтинга не просто проверяет, используют ли непосредственные зависимости вашего хука значения или функции, которые изменятся, но также и то, могут ли какие-либо зависимые функции использовать значения или функции, которые изменятся. - person flyingace; 01.12.2020
comment
Это не обойдется с правилом ESLint b / c, он все равно будет вызывать, что useEffect имеет зависимость: fun. - person flyingace; 01.12.2020
comment
@flyingace, хук useMountEffect по определению предназначен для запуска только при монтировании. Если вам действительно нужен эффект монтирования, изменения зависимостей не имеют значения. react-hooks/exhaustive-deps Правило lint для предоставления зависимостей для useEffect полезно (может предотвратить ошибку), но не в контексте эффекта монтирования. Следовательно, если необходим эффект монтирования, необходимо игнорировать это правило. И если нам нужно проигнорировать это правило, лучше сделать это один раз. - person Ben Carp; 01.12.2020
comment
@BenCarp Я не обсуждаю, каково предназначение функции, я говорю, что фактическое использование без указания fun в качестве зависимости все равно приведет к возникновению ошибки ESLinting. Конечно, это можно проигнорировать или отключить, но я не уверен, какова ценность этого в этом настраиваемом хуке по сравнению с тем, как это делается в функции, которую вы передаете. - person flyingace; 02.12.2020
comment
@flyingace, не могли бы вы поделиться минимальным воспроизводимым примером (возможно, stackblitz или codeandbox), воссоздающим ошибку? - person Ben Carp; 02.12.2020
comment
однозначно лучший ответ! :) - person Petr Odut; 07.12.2020
comment
похоже, вы получаете исчерпывающую ошибку eslint deps для этого синтаксиса. Я попробовал это и обнаружил, что из-за того, что функция всегда не является референциальной (или как вы это говорите), она победила всю цель. don't do this --->>> export const useMountEffect = (fun) => useEffect(fun, [fun]); поэтому я предполагаю, что если вы создадите отдельный модуль и избежите правила в одном месте, которое изолирует и абстрагирует реализацию и работу вокруг - person Raif; 23.03.2021

Передайте пустой массив в качестве второго аргумента 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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

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

person Yangshun Tay    schedule 11.11.2018

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

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

  const unmount = () => {
    console.log('unmounted')
    // ...
  }
  return unmount
}
useEffect(mount, [])

person ecoologic    schedule 12.10.2019

function useOnceCall(cb, condition = true) {
  const isCalledRef = React.useRef(false);

  React.useEffect(() => {
    if (condition && !isCalledRef.current) {
      isCalledRef.current = true;
      cb();
    }
  }, [cb, condition]);
}

и пользуйся этим.

useOnceCall(() => {
  console.log('called');
})

or

useOnceCall(()=>{
  console.log('Fetched Data');
}, isFetched);
person Yasin Tazeoglu    schedule 04.12.2020
comment
Спасибо! Спас мой день. Идеально подходит для однократного вызова функций, но только после загрузки некоторого состояния. - person Davit; 13.04.2021

оставьте массив зависимостей пустым. надеюсь, это поможет вам лучше понять.

   useEffect(() => {
      doSomething()
    }, []) 

пустой массив зависимостей запускается только один раз, при монтировании

useEffect(() => {
  doSomething(value)
}, [value])  

передать value как зависимость. если зависимости изменились с последнего раза, эффект будет запущен снова.

useEffect(() => {
  doSomething(value)
})  

нет зависимости. Это вызывается после каждого рендеринга.

person Shuhad zaman    schedule 16.11.2020