Arhn - архитектура программирования

Как с помощью useEffect пропустить применение эффекта при первоначальном рендеринге?

С помощью новых хуков эффектов React я могу сказать React пропустить применение эффекта, если определенные значения не изменились между повторными рендерингами - пример из документации React:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

Но в приведенном выше примере эффект применяется при первоначальном рендеринге и при последующих повторных рендерингах, где count изменилось. Как я могу сказать React пропустить эффект при первоначальном рендеринге?

06.11.2018

  • Вы смотрели React.useMemo? 09.07.2020

Ответы:


1

Как говорится в руководстве,

Обработчик эффекта, useEffect, добавляет возможность выполнять побочные эффекты из функционального компонента. Он служит той же цели, что и componentDidMount, componentDidUpdate и componentWillUnmount в классах React, но объединен в единый 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)
      fn();
    else
      didMountRef.current = true;
  }, inputs);
}

Кредиты идут на @Tholle за предложение useRef вместо setState.

06.11.2018
  • Где предлагается useRef вместо setState? Я не вижу этого на этой странице и хотел бы понять, почему. 24.06.2019
  • @ rob-gordon Это был комментарий, который был удален после обновления ответа. Причина в том, что useState приведет к ненужному обновлению компонентов. 24.06.2019
  • Этот подход работает, но нарушает правило линтера react-hooks / excustive-deps. Т.е. fn не указывается в массиве deps. У кого-нибудь есть подход, который не нарушает лучших практик React? 02.10.2019
  • @JustinLang React linter rules! = Лучшие практики, они пытаются решить только общие проблемы с перехватами. Правила ESLint неразумны и могут привести к ложным срабатываниям или отрицательным результатам. Пока логика, лежащая в основе ловушки, верна, правило можно безопасно игнорировать с помощью комментариев eslint-disable или eslint-disable-next. В этом случае fn не следует указывать в качестве входных данных. См. Объяснение: reactjs.org/docs/. Если что-то внутри fn вводит deps, скорее всего, они должны быть указаны как inputs напрямую. 02.10.2019
  • Примечание. Если вы используете несколько useEffects, которые проверяют didMountRef, убедитесь, что только последний (внизу) устанавливает для didMountRef значение false. Реакция проходит через useEffects по порядку! 18.04.2021

  • 2

    Вот настраиваемый хук, который просто предоставляет логический флаг, чтобы указать, является ли текущий рендеринг первым рендером (когда компонент был смонтирован). Это примерно то же самое, что и некоторые другие ответы, но вы можете использовать флаг в 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.log('First Render');
        } else {
          console.log('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);
      });
    });
    

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

    23.05.2019
  • Импорт изменен с react-hooks-testing-library на @testing-library/react-hooks github.com/ библиотека-тестирование / библиотека-реагирование-крючки-тестирование / выпуски / 16.07.2019
  • Почему вы выбрали isMount, а не didMount или isMounted? 07.11.2019
  • Думаю, я думал о том, является ли текущий рендер рендером того места, где происходило монтирование. Я согласен, это звучит немного странно, возвращаясь к этому сейчас. Но это верно при первом рендеринге и ложно после, поэтому ваши предложения могут вводить в заблуждение. isFirstRender может работать. 07.11.2019
  • Спасибо за крючок! Я согласен с @ScottyWaggoner, isFirstRender - лучшее имя 11.04.2020
  • Спасибо за Ваш ответ. Я использую ваш крючок так же, как первое значение false, после установки true и переименования didMount :) 30.05.2020
  • Добавление теста - ОГОНЬ! Отличный ответ. 29.01.2021

  • 3

    Я нашел решение, более простое и не требующее использования другого крючка, но у него есть недостатки.

    useEffect(() => {
      // skip initial render
      return () => {
        // do something with dependency
      }
    }, [dependency])
    

    Это просто пример того, что есть другие способы сделать это, если ваш случай очень простой.

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

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

    Редактировать:

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

    11.04.2020
  • Я только что кое-что узнал. Я не думал, что это сработает, потом попробовал, и это действительно так. Спасибо! 12.04.2020
  • React должен предоставить лучший способ для этого, но этот вопрос открыт на Github, и предлагается написать собственное решение самостоятельно (что совершенно бессмысленно). 12.07.2020
  • Проблема с этим в том, что он также будет выполняться при размонтировании, что не идеально. 22.07.2020
  • @ncesar, если это был единственный ответ, который сработал, возможно, вы делаете что-то не так, потому что другой ответ правильный, а мой предназначен только для обучения 04.12.2020

  • 4

    Я использую обычную переменную состояния вместо ссылки.

    // 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.

    29.03.2019
  • useRef лучше подходит для этого, потому что useState вызовет дополнительную и бесполезную визуализацию компонента: codeandbox.io / embed / youthful-goldberg-pz3cx 19.08.2019
  • у вас есть ошибка в вашей 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 13.11.2020

  • 5

    Дружественный хук 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)
    }
    
    12.05.2020

    6

    Вот моя реализация, основанная на ответе 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.

     Отредактируйте 53179075 / with-useeffect-how-can-i-skip-apply  -an-effect-on-the-initial-render

    02.02.2021

    7

    Нижеприведенное решение похоже на приведенное выше, но я предпочитаю немного более чистый способ.

    const [isMount, setIsMount] = useState(true);
    useEffect(()=>{
            if(isMount){
                setIsMount(false);
                return;
            }
            
            //Do anything here for 2nd render onwards
    }, [args])
    
    13.01.2021
  • Это не идеальный способ сделать это, поскольку установка состояния вызывает другой рендер. 31.01.2021

  • 8

    Я собирался прокомментировать принятый на данный момент ответ, но не хватило места!

    Во-первых, при использовании функциональных компонентов важно отказаться от мышления в терминах событий жизненного цикла. Подумайте об изменениях свойств / состояний. У меня была аналогичная ситуация, когда я хотел, чтобы срабатывала только конкретная 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/

    06.02.2021
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..