Хук useEffect React будет запускать переданную функцию при каждом изменении. Это можно оптимизировать, чтобы он мог вызывать только при изменении желаемых свойств.
Что, если я хочу вызвать функцию инициализации из componentDidMount и не вызывать ее снова при изменениях? Допустим, я хочу загрузить объект, но функции загрузки не нужны данные из компонента. Как это сделать с помощью хука useEffect?
class MyComponent extends React.PureComponent {
componentDidMount() {
loadDataOnlyOnce();
}
render() { ... }
}
С крючками это может выглядеть так:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire on every change :(
}, [...???]);
return (...);
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Если вы хотите запустить функцию, заданную useEffect, только после первоначального рендеринга, вы можете указать пустой массив в качестве второго аргумента.
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce();
}, []);
return <div> {/* ... */} </div>;
}
да ... подробнее о пропуске задокументировано здесь: reactjs.org/docs/…
Это кажется самым простым ответом, но ESLint жалуется ... см. Другой ответ в этом потоке stackoverflow.com/a/56767883/1550587
Просто передайте loadDataOnlyOnce в массив зависимостей. Это работает?
Нет, потому что при изменении loadDataOnlyOnce (не в этом примере, но lint все равно не будет жаловаться на нелокальные переменные), он повторно запустит эффект. Решением было бы либо создать отдельную функцию для крючка, как в другом ответе здесь (эффективно обмануть ESLint), либо иметь useRef с логическим значением, которое вы установили после первого запуска, и не запускать снова, если оно установлено.
Что не так с useEffect(loadDataOnlyOnce, []);?
Передайте пустой массив в качестве второго аргумента в 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 будет сравнивать после каждого рендеринга массива и увидит, что ничего не было изменено, таким образом вызывая обратный вызов только после первого рендеринга.
Запуск функции только один раз после монтирования компонента является настолько распространенным шаблоном, что оправдывает собственный крючок, скрывающий детали реализации.
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. Это должно быть приемлемое решение, поскольку оно не противоречит правилу ESLint.
Спасибо @Dynalon и Mikado68 :-). Решение - привилегия ОП. Меня уведомили о ваших комментариях, а вот OP - нет. Вы можете предложить ему это, непосредственно прокомментировав вопрос.
@ Mikado68 см. Мой комментарий выше
Теперь вы можете использовать useMount, когда вашей функции эффекта требуется что-то от реквизита, но никогда не нужно запускать снова, даже если это значение изменится без предупреждения линтера: useEffect(()=>console.info(props.val),[]) не будет иметь предупреждения о зависимости, но useMount(()=>console.info(props.val)) не вызовет предупреждения, но «работает». Я не уверен, что возникнет проблема с параллельным режимом.
Если я чего-то не упускаю, ESLint по-прежнему жалуется внутри настраиваемого хука useMountEffect. Я отключил его с помощью eslint-disable-line, а лучше всего один раз в новом файле перехвата, но все равно немного раздражает. Похоже, это должно быть разрешено правилом.
@ DávidMolnár, см. Комментарии Dynalon и Mikado68 выше.
Мне нравится этот :) Причина та же, что и ранее; правило ESLint не будет жаловаться на это, плюс его название легче понять, чем пустой массив
Это решение, которое я искал, спасибо @BenCarp
Не совсем понимаю ... "react-hooks/exhaustive-deps" до сих пор скулит по поводу пустого массива в const useMountEffect = (fun) => useEffect(fun, [])
@VincentChan, он скулит о пустом массиве, а скулит только о. Он не скулит, когда вы вызываете useMountEffect. Очевидно, вы можете проигнорировать это, и вам нужно проигнорировать только один раз. Вы скрываете этот шум / беспорядок от своих компонентов
Спасибо! Хотя я думаю, что это указывает на недостаток в "react-hooks/exhaustive-deps", особенно потому, что это канонический способ запускать вещи на монтировании. Это «решение» функционально перемещает проблему из компонента в другое место вместо того, чтобы принципиально решать проблему с пустыми зависимостями.
А как насчет того, чтобы вызвать функцию с аргументами?
Здесь есть многочисленные комментарии о недостатках в правилах ESLint для react-hooks/exhaustive-deps, и я здесь пытаюсь найти решение проблемы, аналогичной проблеме OP. Но я не уверен, что здесь действительно ошибаются правила ESLint. Это правило линтинга не просто проверяет, используют ли непосредственные зависимости вашего хука значения или функции, которые изменятся, но также и то, могут ли какие-либо зависимые функции использовать значения или функции, которые изменятся.
Это не «обойдет» правило ESLint b / c, оно все равно будет вызывать, что useEffect имеет зависимость: fun.
@flyingace, хук useMountEffect по определению предназначен для запуска только при монтировании. Если вам действительно нужен эффект монтирования, изменения зависимостей не имеют значения. Правило react-hooks/exhaustive-deps lint для предоставления зависимостей для useEffect имеет ценность (может предотвратить ошибку), но не в контексте эффекта монтирования. Следовательно, если необходим эффект монтирования, необходимо игнорировать это правило. И если нам нужно проигнорировать это правило, лучше сделать это один раз.
@BenCarp Я не обсуждаю, каково предназначение функции, я говорю, что фактическое использование без перечисления fun в качестве зависимости все равно приведет к возникновению ошибки ESLinting. Конечно, его можно проигнорировать или отключить, но я не уверен, какова ценность этого в этом настраиваемом хуке по сравнению с тем, как это делается в функции, которую вы передаете.
@flyingace, не могли бы вы поделиться минимальным воспроизводимым примером (возможно, stackblitz или codeandbox), воссоздающим ошибку?
однозначно лучший ответ! :)
похоже, вы получаете исчерпывающую ошибку eslint для этого синтаксиса. Я попробовал это и обнаружил, что из-за того, что функция всегда не является референциальной (или как вы это говорите), она победила всю цель. don't do this --->>> export const useMountEffect = (fun) => useEffect(fun, [fun]);, поэтому я предполагаю, что если вы создадите отдельный модуль и избежите правила в одном месте, которое изолирует и абстрагирует реализацию и работу вокруг
Любая идея, как добавить ввод для этого в машинописный текст?
@kblst, введите параметр cb как React.EffectCallback
Мне нравится определять функцию 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);
Спасибо! Спас мой день. Идеально подходит для однократного вызова функций, но только после загрузки некоторого состояния.
Вот моя версия ответа Ясина.
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.
@JusticeBringer Я бы сказал не только для новичков. Это непростая концепция.
В качестве альтернативы, если есть параметры, которые вы используете для получения данных (например, идентификатор пользователя), вы можете передать идентификатор пользователя в этом массиве, и если он изменится, компонент будет повторно получать данные. Многие варианты использования будут работать именно так.