Как вызвать функцию загрузки с помощью 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 (...);
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
372
0
328 947
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

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

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

  return <div> {/* ... */} </div>;
}

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

trixn 02.11.2018 16:03

да ... подробнее о пропуске задокументировано здесь: reactjs.org/docs/…

Melounek 02.11.2018 16:05

Это кажется самым простым ответом, но ESLint жалуется ... см. Другой ответ в этом потоке stackoverflow.com/a/56767883/1550587

Simon Hutchison 02.05.2020 04:49

Просто передайте loadDataOnlyOnce в массив зависимостей. Это работает?

jpmarks 03.07.2020 07:56

Нет, потому что при изменении loadDataOnlyOnce (не в этом примере, но lint все равно не будет жаловаться на нелокальные переменные), он повторно запустит эффект. Решением было бы либо создать отдельную функцию для крючка, как в другом ответе здесь (эффективно обмануть ESLint), либо иметь useRef с логическим значением, которое вы установили после первого запуска, и не запускать снова, если оно установлено.

riv 01.12.2020 03:19

Что не так с useEffect(loadDataOnlyOnce, []);?

Software Engineer 01.01.2021 16:05

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

This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

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

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>

TL; DR

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

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

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

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

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

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

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

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

Я очень предпочитаю ваш ответ, так как правило ESLint «react-hooks / excustive-deps» всегда будет терпеть неудачу в пустых списках зависимостей. И, например, знаменитый шаблон приложения create-response-app будет применять это правило.

Dynalon 24.07.2019 11:02

Полностью согласен с @Dynalon. Это должно быть приемлемое решение, поскольку оно не противоречит правилу ESLint.

Mikado68 12.08.2019 10:26

Спасибо @Dynalon и Mikado68 :-). Решение - привилегия ОП. Меня уведомили о ваших комментариях, а вот OP - нет. Вы можете предложить ему это, непосредственно прокомментировав вопрос.

Ben Carp 12.08.2019 11:35

@ Mikado68 см. Мой комментарий выше

Ben Carp 12.08.2019 11:35

Теперь вы можете использовать useMount, когда вашей функции эффекта требуется что-то от реквизита, но никогда не нужно запускать снова, даже если это значение изменится без предупреждения линтера: useEffect(()=>console.info(props.val),[]) не будет иметь предупреждения о зависимости, но useMount(()=>console.info(props.val)) не вызовет предупреждения, но «работает». Я не уверен, что возникнет проблема с параллельным режимом.

HMR 08.10.2019 18:50

Если я чего-то не упускаю, ESLint по-прежнему жалуется внутри настраиваемого хука useMountEffect. Я отключил его с помощью eslint-disable-line, а лучше всего один раз в новом файле перехвата, но все равно немного раздражает. Похоже, это должно быть разрешено правилом.

dramus 06.01.2020 17:56

@ DávidMolnár, см. Комментарии Dynalon и Mikado68 выше.

Ben Carp 30.01.2020 04:17

Мне нравится этот :) Причина та же, что и ранее; правило ESLint не будет жаловаться на это, плюс его название легче понять, чем пустой массив

Frexuz 31.03.2020 05:01

Это решение, которое я искал, спасибо @BenCarp

Sarang PM 15.07.2020 14:38

Не совсем понимаю ... "react-hooks/exhaustive-deps" до сих пор скулит по поводу пустого массива в const useMountEffect = (fun) => useEffect(fun, [])

Vincent Chan 04.09.2020 06:03

@VincentChan, он скулит о пустом массиве, а скулит только о. Он не скулит, когда вы вызываете useMountEffect. Очевидно, вы можете проигнорировать это, и вам нужно проигнорировать только один раз. Вы скрываете этот шум / беспорядок от своих компонентов

Ben Carp 04.09.2020 08:47

Спасибо! Хотя я думаю, что это указывает на недостаток в "react-hooks/exhaustive-deps", особенно потому, что это канонический способ запускать вещи на монтировании. Это «решение» функционально перемещает проблему из компонента в другое место вместо того, чтобы принципиально решать проблему с пустыми зависимостями.

Vincent Chan 07.09.2020 23:31

А как насчет того, чтобы вызвать функцию с аргументами?

Brijesh Prasad 22.10.2020 16:04

Здесь есть многочисленные комментарии о недостатках в правилах ESLint для react-hooks/exhaustive-deps, и я здесь пытаюсь найти решение проблемы, аналогичной проблеме OP. Но я не уверен, что здесь действительно ошибаются правила ESLint. Это правило линтинга не просто проверяет, используют ли непосредственные зависимости вашего хука значения или функции, которые изменятся, но также и то, могут ли какие-либо зависимые функции использовать значения или функции, которые изменятся.

flyingace 01.12.2020 19:12

Это не «обойдет» правило ESLint b / c, оно все равно будет вызывать, что useEffect имеет зависимость: fun.

flyingace 01.12.2020 19:26

@flyingace, хук useMountEffect по определению предназначен для запуска только при монтировании. Если вам действительно нужен эффект монтирования, изменения зависимостей не имеют значения. Правило react-hooks/exhaustive-deps lint для предоставления зависимостей для useEffect имеет ценность (может предотвратить ошибку), но не в контексте эффекта монтирования. Следовательно, если необходим эффект монтирования, необходимо игнорировать это правило. И если нам нужно проигнорировать это правило, лучше сделать это один раз.

Ben Carp 01.12.2020 19:42

@BenCarp Я не обсуждаю, каково предназначение функции, я говорю, что фактическое использование без перечисления fun в качестве зависимости все равно приведет к возникновению ошибки ESLinting. Конечно, его можно проигнорировать или отключить, но я не уверен, какова ценность этого в этом настраиваемом хуке по сравнению с тем, как это делается в функции, которую вы передаете.

flyingace 02.12.2020 00:17

@flyingace, не могли бы вы поделиться минимальным воспроизводимым примером (возможно, stackblitz или codeandbox), воссоздающим ошибку?

Ben Carp 02.12.2020 04:40

однозначно лучший ответ! :)

Petr Odut 07.12.2020 15:38

похоже, вы получаете исчерпывающую ошибку eslint для этого синтаксиса. Я попробовал это и обнаружил, что из-за того, что функция всегда не является референциальной (или как вы это говорите), она победила всю цель. don't do this --->>> export const useMountEffect = (fun) => useEffect(fun, [fun]);, поэтому я предполагаю, что если вы создадите отдельный модуль и избежите правила в одном месте, которое изолирует и абстрагирует реализацию и работу вокруг

Raif 23.03.2021 14:29

Любая идея, как добавить ввод для этого в машинописный текст?

kblst 13.11.2021 14:35

@kblst, введите параметр cb как React.EffectCallback

Ben Carp 14.11.2021 11:34

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

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

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

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

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

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

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

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

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

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

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

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

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

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

или

useOnceCall(()=>{
  console.info('Fetched Data');
}, isFetched);

Спасибо! Спас мой день. Идеально подходит для однократного вызова функций, но только после загрузки некоторого состояния.

Davit 13.04.2021 09:17

Вот моя версия ответа Ясина.

import {useEffect, useRef} from 'react';

const useOnceEffect = (effect: () => void) => {
  const initialRef = useRef(true);

  useEffect(() => {
    if (!initialRef.current) {
      return;
    }
    initialRef.current = false;
    effect();
  }, [effect]);
};

export default useOnceEffect;

Использование:

useOnceEffect(
  useCallback(() => {
    nonHookFunc(deps1, deps2);
  }, [deps1, deps2])
);

Мы должны перестать думать о методах жизненного цикла компонентов (например, componentDidMount). Мы должны начать думать эффектами. Эффекты React отличаются от методов жизненного цикла классов в старом стиле.

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

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

1-е решение (с жалобой на ESLint)

Итак, первое решение для вашего примера будет следующим:

function MyComponent() {

    const loadDataOnlyOnce = () => {
      console.info("loadDataOnlyOnce");
    };

    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}

Но тогда плагин React Hooks ESLint будет жаловаться примерно на это:

React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.

Сначала это предупреждение кажется раздражающим, но, пожалуйста, не игнорируйте его. Это помогает вам лучше писать код.

2-е решение (правильный путь, если зависимость не зависит от компонента)

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

Мы должны сохранять ту же ссылку на loadDataOnlyOnce во время циклов рендеринга.

Так что просто переместите определение функции выше:

const loadDataOnlyOnce = () => {
  console.info("loadDataOnlyOnce");
};

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire only on first render
    }, []);
    return (...);
}

3-е решение (правильный путь, если зависимость зависит от компонента)

Если зависимость эффекта (loadDataOnlyOnce) зависит от компонента (нужны реквизиты или состояние), есть встроенный в React useCallback-Hook.

Элементарный смысл useCallback-Hook состоит в том, чтобы сохранять ссылку на функцию идентичной во время циклов рендеринга.

function MyComponent() {
    const [state, setState] = useState("state");

    const loadDataOnlyOnce = useCallback(() => {
      console.info(`I need ${state}!!`);
    }, [state]);

    useEffect(() => {
        loadDataOnlyOnce(); // // this will fire only when loadDataOnlyOnce-reference changes
    }, [loadDataOnlyOnce]);
    return (...);
}

Этот ответ заслуживает большего внимания начинающих разработчиков React.

Justice Bringer 21.10.2021 18:36

@JusticeBringer Я бы сказал не только для новичков. Это непростая концепция.

Rodrigo Borba 05.11.2021 21:28

Другие вопросы по теме