С новыми хуками эффектов React я могу сказать React пропустить применение эффекта, если определенные значения не изменились между повторными рендерингами - пример из документации React:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
Но в приведенном выше примере эффект применяется при первоначальном рендеринге и при последующих повторных рендерингах, когда был изменен count. Как я могу сказать React пропустить эффект при первоначальном рендеринге?





Как говорится в руководстве,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
В этом примере из руководства ожидается, что count равен 0 только при начальном рендеринге:
const [count, setCount] = useState(0);
Так что он будет работать как componentDidUpdate с дополнительной проверкой:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
По сути, вот как может работать кастомный хук, который можно использовать вместо useEffect:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current)
return fn();
else
didMountRef.current = true;
}, inputs);
}
Кредиты идут на @Tholle за предложение useRef вместо setState.
Где предложение для useRef вместо setState? Я не вижу этого на этой странице и хотел бы понять, почему.
@ rob-gordon Это был комментарий, который был удален после обновления ответа. Причина в том, что useState приведет к ненужному обновлению компонентов.
Этот подход работает, но нарушает правило линтера react-hooks / excustive-deps. Т.е. fn не указывается в массиве deps. У кого-нибудь есть подход, который не нарушает лучших практик React?
@JustinLang React linter rules! = Лучшие практики, они пытаются решить только общие проблемы с перехватами. Правила ESLint неразумны и могут привести к ложным срабатываниям или отрицательным результатам. Пока логика, лежащая в основе ловушки, верна, правило можно безопасно игнорировать с помощью комментариев eslint-disable или eslint-disable-next. В этом случае fn не должен использоваться в качестве входных данных. См. Объяснение, reactjs.org/docs/…. Если что-то внутри fn вводит deps, это больше похоже на то, что они должны быть предоставлены напрямую как inputs.
Я использую обычную переменную состояния вместо ссылки.
// Initializing didMount as false
const [didMount, setDidMount] = useState(false)
// Setting didMount to true upon mounting
useEffect(() => { setDidMount(true) }, [])
// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
Мы можем реорганизовать логику didMount как настраиваемую ловушку, подобную этой.
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
Наконец, мы можем использовать его в нашем компоненте вот так.
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
ОБНОВИТЬ Использование хука useRef, чтобы избежать лишнего повторного рендеринга (спасибо @TomEsterez за предложение)
На этот раз наш пользовательский хук возвращает функцию, возвращающую текущее значение нашего ref. Вы тоже можете использовать ссылку напрямую, но мне это больше нравится.
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
использование
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
Кстати, мне никогда не приходилось использовать этот хук, и, вероятно, есть лучшие способы справиться с этим, которые больше похожи на модель программирования React.
useRef лучше подходит для этого, потому что useState вызовет дополнительную и бесполезную визуализацию компонента: codeandbox.io/embed/youthful-goldberg-pz3cxу вас есть ошибка в вашей функции useDidMount. Вы должны заключить mountRef.current = true в useEffect() с фигурными скобками { ...}. Оставить их - все равно что записать return mountRef.current = true, и это вызовет ошибку типа An effect function must not return anything besides a function, which is used for clean-up. You returned: true.
Вот настраиваемый хук, который просто предоставляет логический флаг, чтобы указать, является ли текущий рендеринг первым рендером (когда компонент был смонтирован). Это примерно то же самое, что и некоторые другие ответы, но вы можете использовать флаг в useEffect или функции рендеринга или в любом другом месте в нужном вам компоненте. Может, кто-нибудь предложит имя получше.
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
Вы можете использовать это как:
import React, { useEffect } from 'react';
import { useIsMount } from './useIsMount';
const MyComponent = () => {
const isMount = useIsMount();
useEffect(() => {
if (isMount) {
console.info('First Render');
} else {
console.info('Subsequent Render');
}
});
return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
А вот и тест, если вам интересно:
import { renderHook } from '@testing-library/react-hooks';
import { useIsMount } from '../useIsMount';
describe('useIsMount', () => {
it('should be true on first render and false after', () => {
const { result, rerender } = renderHook(() => useIsMount());
expect(result.current).toEqual(true);
rerender();
expect(result.current).toEqual(false);
rerender();
expect(result.current).toEqual(false);
});
});
Нашим вариантом использования было скрытие анимированных элементов, если исходные свойства указывают, что они должны быть скрыты. На более поздних рендерах, если реквизит изменился, мы хотели, чтобы элементы анимировались.
Импорт изменен с react-hooks-testing-library на @testing-library/react-hooksgithub.com/testing-library/react-hooks-testing-library/relea ses /…
Почему вы выбрали isMount, а не didMount или isMounted?
Думаю, я думал о том, является ли текущий рендер рендером того места, где происходило монтирование. Я согласен, это звучит немного странно, возвращаясь к этому сейчас. Но это верно при первом рендеринге и ложно после, поэтому ваши предложения могут вводить в заблуждение. isFirstRender может работать.
Спасибо за крючок! Я согласен с @ScottyWaggoner, isFirstRender - лучшее имя
Спасибо за Ваш ответ. Я использую ваш крючок так же, как первое значение false, после установки true и переименования didMount :)
Добавление теста - ОГОНЬ! Отличный ответ.
Спасибо вам большое за это ! Я полностью согласен с названием. isFirstRender имеет смысл. Я уже использовал его в своем проекте с таким названием =)
Потрясающий материал !!
Я нашел более простое решение, в котором нет необходимости использовать другой крючок, но у него есть недостатки.
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
Это просто пример того, что есть другие способы сделать это, если ваш случай очень прост.
Недостатком этого является то, что вы не можете получить эффект очистки и будет выполняться только тогда, когда массив зависимостей изменится во второй раз.
Это не рекомендуется использовать, и вы должны использовать то, что говорят другие ответы, но я добавил это только здесь, чтобы люди знали, что есть несколько способов сделать это.
Чтобы было понятнее, подход ты не должен использовать это к решению проблемы в вопросе (пропуск начального рендеринга) предназначен только для обучающих целей, которые показывают, что вы можете делать то же самое по-разному. Если вам нужно пропустить начальную визуализацию, используйте этот подход для других ответов.
Я только что кое-что узнал. Я не думал, что это сработает, потом попробовал, и это действительно так. Спасибо!
React должен предоставить лучший способ для этого, но этот вопрос открыт на Github, и предлагается написать собственное решение самостоятельно (что совершенно бессмысленно).
Проблема с этим в том, что он также будет выполняться при размонтировании, что не идеально.
@ncesar, если это был единственный ответ, который сработал, возможно, вы делаете что-то не так, потому что другой ответ правильный, а мой предназначен только для обучения целям
Дружественный хук TypeScript и CRA, замените его на useEffect, этот хук работает как useEffect, но не срабатывает, пока происходит первый рендеринг.
import * as React from 'react'
export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
const initializeRef = React.useRef<boolean>(false)
React.useEffect((...args) => {
if (initializeRef.current) {
cb(...args)
} else {
initializeRef.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dep)
}
Нижеприведенное решение похоже на приведенное выше, но я предпочитаю немного более чистый способ.
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if (isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
Это не идеальный способ сделать это, поскольку установка состояния вызывает другой рендеринг.
Вот моя реализация, основанная на отвечать Estus Flask, написанном на Typescript. Он также поддерживает обратный вызов очистки.
import { DependencyList, EffectCallback, useEffect, useRef } from 'react';
export function useDidUpdateEffect(
effect: EffectCallback,
deps?: DependencyList
) {
// a flag to check if the component did mount (first render's passed)
// it's unrelated to the rendering process so we don't useState here
const didMountRef = useRef(false);
// effect callback runs when the dependency array changes, it also runs
// after the component mounted for the first time.
useEffect(() => {
// if so, mark the component as mounted and skip the first effect call
if (!didMountRef.current) {
didMountRef.current = true;
} else {
// subsequent useEffect callback invocations will execute the effect as normal
return effect();
}
}, deps);
}
Живая демонстрация ниже демонстрирует разницу между хуками useEffect и useDidUpdateEffect.
Я собирался прокомментировать принятый на данный момент ответ, но не хватило места!
Во-первых, при использовании функциональных компонентов важно отказаться от мышления в терминах событий жизненного цикла. Подумайте об изменениях свойств / состояний. У меня была аналогичная ситуация, когда я хотел, чтобы срабатывала только конкретная функция useEffect, когда конкретная опора (parentValue в моем случае) меняет свое начальное состояние. Итак, я создал реф, основанный на его начальном значении:
const parentValueRef = useRef(parentValue);
а затем включил следующее в начало useEffect fn:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(Как правило, не запускайте эффект, если parentValue не изменился. Обновите ссылку, если она изменилась, готовность к следующей проверке, и продолжайте запускать эффект)
Таким образом, хотя другие предлагаемые решения решат конкретный вариант использования, который вы указали, в конечном итоге они помогут изменить то, как вы думаете о функциональных компонентах.
Думайте о них как о рендеринге компонента на основе некоторых свойств.
Если вам действительно нужно какое-то локальное состояние, то useState предоставит это, но не предполагать, ваша проблема будет решена путем сохранения локального состояния.
Если у вас есть код, который изменит ваши реквизиты во время рендеринга, этот `` побочный эффект '' необходимо обернуть в useEffect, но его цель - получить чистый рендер, на который не влияют какие-либо изменения во время рендеринга. . Хук useEffect будет запущен после завершения рендеринга и, как вы указали, он будет запускаться при каждом рендеринге - если только второй параметр не используется для предоставления списка свойств / состояний, чтобы определить, какие измененные элементы приведут к его изменению. выполнить последующие разы.
Удачи на пути к функциональным компонентам / крючкам! Иногда необходимо отучиться чему-то, чтобы освоить новый способ ведения дел :) Это отличный праймер: https://overrected.io/a-complete-guide-to-useeffect/
Разрешите представить вам реагировать-использовать.
Хочу бежать:
только после первого рендера? useUpdateEffect
только один раз? useEffectOnce
это первое крепление?? useFirstMountState
Хотите запустить эффект с глубокое сравнение, поверхностное сравнение или дроссель? Намного больше здесь.
Не хотите устанавливать библиотеку? Проверьте код и скопируйте. (может быть, star для хороших ребят тоже)
Вы смотрели
React.useMemo?