Как использовать componentWillMount () в React Hooks?

176

В официальных документах React упоминается -

Если вы знакомы с методами жизненного цикла класса React, вы можете думать о useEffect Hook как о компонентах componentDidMount, componentDidUpdate и componentWillUnmount вместе.

Мой вопрос - как мы можем использовать componentWillMount()метод жизненного цикла в крючке?

Abrar
источник

Ответы:

341

Вы не можете использовать любого из существующих методов жизненного цикла ( componentDidMount, componentDidUpdate, и componentWillUnmountт.д.) в крюке. Они могут использоваться только в компонентах класса. А с крючками вы можете использовать только функциональные компоненты. Строка ниже взята из документа React:

Если вы знакомы с React методы жизненного цикла класса, вы можете думать о useEffectкрючке componentDidMount, componentDidUpdateи в componentWillUnmountсочетании.

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

Код внутри componentDidMountзапускается один раз, когда компонент монтируется. useEffectкрюк-эквивалент для этого поведения

useEffect(() => {
  // Your code here
}, []);

Обратите внимание на второй параметр здесь (пустой массив). Это будет работать только один раз.

Без второго параметраuseEffect крючок будет вызываться каждый рендер компонент , который может быть опасным.

useEffect(() => {
  // Your code here
});

componentWillUnmountиспользуется для очистки (например, удаление слушателей событий, отмена таймера и т. д.). Допустим, вы добавляете прослушиватель событий componentDidMountи удаляете его, componentWillUnmountкак показано ниже.

componentDidMount() {
  window.addEventListener('mousemove', () => {})
}

componentWillUnmount() {
  window.removeEventListener('mousemove', () => {})
}

Хук-эквивалент вышеприведенного кода будет следующим

useEffect(() => {
  window.addEventListener('mousemove', () => {});

  // returned function will be called on component unmount 
  return () => {
    window.removeEventListener('mousemove', () => {})
  }
}, [])
Бхаскар Гьян Вардхан
источник
184
Хорошее объяснение для других событий жизненного цикла, но это не вопрос конкретно об альтернативе componentWillMount ().
Шираз
3
В примерах PanResponder, которые я видел, кажется, что componentWillMount необходим, иначе вы получите неопределенные panHandlers.
Dror Bar
2
Теперь я действительно понимаю useEffect()функцию, спасибо.
Ихароб Аль Асими
67
Почему это принятый ответ? Вы не упомянули эквивалент крюка дляcomponentWillMount
Mykybo
3
@techexpert Вопрос, заданный для эквивалента componentWillMount, нет componentWillUnmount. Этот ответ действительно не отвечает на вопрос вообще, а просто повторяет то, что ОП уже подразумевало знать.
JoshuaCWebDeveloper
62

хук useComponentDidMount

В большинстве случаев useComponentDidMountэто инструмент для использования. Он будет запущен только один раз после монтирования компонента (начальная визуализация).

 const useComponentDidMount = func => useEffect(func, []);

useComponentWillMount

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

const useComponentWillMount = func => {
  const willMount = useRef(true);

  if (willMount.current) {
    func();
  }

  willMount.current = false;
};

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

демонстрация

const Component = (props) => {
  useComponentWillMount(() => console.log("Runs only once before component mounts"));
  useComponentDidMount(() => console.log("Runs only once after component mounts"));
  ...

  return (
    <div>{...}</div>
  );
}

Полная демонстрация

Бен Карп
источник
18
Это единственный ответ, который отвечает на вопрос и имеет смысл. Спасибо!
Чумаков
3
Единственная проблема в том, что вы получаете дополнительный рендер из-за обновления состояния. Используя вместо этого ref, вы получите желаемое поведение без дополнительного рендера: `const useComponentWillMount = func => {const willMount = useRef (true); useEffect (() => {willMount.current = false;}, []); if (willMount.current) {func (); }}; `
remix23
2
Эта функциональная реализация componentWillMountоснована на useEffectдвух проблемах. Первый заключается в том, что в функциональных компонентах нет жизненного цикла монтирования, оба хуков будут работать после рендеринга компонента, поэтому Runs only once before component mountsвводит в заблуждение. Второе - это то, что componentWillMountвызывается при рендеринге сервера, а useEffectэто не так. Многие библиотеки по-прежнему полагаются, UNSAFE_componentWillMountпотому что в настоящее время это единственный способ вызвать побочный эффект на стороне сервера.
Паоло Моретти
2
@PaoloMoretti, спасибо. Этот хук componentWillMount не является точным эквивалентом жизненного цикла componentWillMount в компоненте класса. Однако функция, которая передается ей, будет запускаться немедленно, только при первом ее вызове. Практически это означает, что он будет запущен до того, как будет обработан, и даже до того, как он вернет значение в первый раз. Можем ли мы договориться об этом? Я согласен, что использование имени componentWillMount не является идеальным, поскольку это имя несет определенное значение в версии жизненного цикла класса. Возможно, мне лучше назвать это «useRunPreMount».
Бен Карп
1
@PaoloMoretti, я не совсем понимаю. Я не работаю с SSR, но понимаю, что в SSR компонентWillMount запускается дважды - один раз на сервере и один раз на клиенте. Я думаю, что то же самое верно для обратного вызова, который передается useComponentDidMount. useComponentDidMount передает useEffect для прекращения вызова обратного вызова. До выполнения обратного вызова useEffect функция компонента будет выполняться дважды - один раз на сервере и один раз на клиенте. Разве это не так?
Бен Карп
53

По данным responsejs.org, компонентWillMount не будет поддерживаться в будущем. https://reactjs.org/docs/react-component.html#unsafe_componentwillmount

Нет необходимости использовать componentWillMount.

Если вы хотите что-то сделать до монтирования компонента, просто сделайте это в конструкторе ().

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

Сетевые запросы могут быть выполнены в componentDidMount.

Надеюсь, поможет.


обновлено 08/03/2019

Причина, по которой вы запрашиваете componentWillMount, вероятно, заключается в том, что вы хотите инициализировать состояние перед рендерингом.

Просто сделайте это в useState.

const helloWorld=()=>{
    const [value,setValue]=useState(0) //initialize your state here
    return <p>{value}</p>
}
export default helloWorld;

или, может быть, вы хотите запустить функцию в componentWillMount, например, если ваш оригинальный код выглядит следующим образом:

componentWillMount(){
  console.log('componentWillMount')
}

с помощью hook, все, что вам нужно сделать, это удалить метод жизненного цикла:

const hookComponent=()=>{
    console.log('componentWillMount')
    return <p>you have transfered componeWillMount from class component into hook </p>
}

Я просто хочу добавить что-то к первому ответу об использовании эффекта.

useEffect(()=>{})

useEffect запускается для каждого рендера, это комбинация componentDidUpdate, componentDidMount и ComponentWillUnmount.

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

Если мы добавим пустой массив в useEffect, он будет запущен сразу после монтирования компонента. Это потому, что useEffect будет сравнивать массив, который вы ему передали. Так что это не должен быть пустой массив. Это может быть массив, который не изменяется. Например, это может быть [1,2,3] или ['1,2']. useEffect по-прежнему работает только при монтировании компонента.

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

Я создал образец для крючка. Пожалуйста, проверьте это.

https://codesandbox.io/s/kw6xj153wr


Обновлено 21/08/2019

Это был белый цвет, так как я написал ответ выше. Есть кое-что, на что, я думаю, нужно обратить внимание. Когда вы используете

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

Когда реакция сравнивает значения, переданные в массив [], он использует Object.is () для сравнения. Если вы передаете объект, например,

useEffect(()=>{},[{name:'Tom'}])

Это точно так же, как:

useEffect(()=>{})

Он будет перерисовываться каждый раз, потому что когда Object.is () сравнивает объект, он сравнивает его ссылку, а не само значение. Это то же самое, что и то, почему {} === {} возвращает false, потому что их ссылки разные. Если вы все еще хотите сравнить сам объект, а не ссылку, вы можете сделать что-то вроде этого:

useEffect(()=>{},[JSON.stringify({name:'Tom'})])
MING WU
источник
17
и вопрос был в том, как это реализовать с помощью хуков
Шубхам Хатри
3
но вам не нужно реализовывать это с помощью хуков, потому что это не будет поддерживаться. Не нужно учиться делать это с помощью крючков.
MING WU
1
Теперь, когда вы упомянули, что componentDidMount является правильным жизненным циклом для использования, вы могли бы добавить, как реализовать это в своем ответе, и тогда ваш ответ будет иметь больше смысла, чем принятый ответ
Шубхам Хатри
8
Конечно, это должен быть принятый ответ - он объясняет, что ComponentWillMount недоступен в парадигме хуков. Инициализация в функциональных компонентах упрощена - она ​​просто должна быть частью функции
Шираз
1
Как это то же самое, что и componentWillMount? Если вы добавите код в функциональный компонент, он будет запускать каждый рендер, а не только когда компонент собирается смонтировать.
Перекодировать
13

useLayoutEffectможет выполнить это с помощью пустого набора наблюдателей ( []), если функциональность на самом деле похожа componentWillMount- он будет запущен до того, как первый контент попадет в DOM - хотя на самом деле есть два обновления, но они синхронны перед отображением на экране.

например:


function MyComponent({ ...andItsProps }) {
     useLayoutEffect(()=> {
          console.log('I am about to render!');
     },[]);

     return (<div>some content</div>);
}

Преимущество по сравнению useStateс инициализатором / установщиком или useEffectто, что он может вычислить проход рендеринга, не приводит к фактическим повторным рендерингам в DOM, которые пользователь заметит, и он запускается до первого заметного рендеринга, что не относится к случаю. useEffect, Недостатком является, конечно, небольшая задержка в вашем первом рендере, так как проверка / обновление должны выполняться перед рисованием на экране. Это действительно зависит от вашего варианта использования, хотя.

Лично я думаю, что useMemoэто хорошо в некоторых нишевых случаях, когда вам нужно сделать что-то тяжелое - если вы помните, что это исключение против нормы.

rob2d
источник
3
useLayoutEffect - это путь !!!! Это ответ на мой вопрос, касающийся проверки, вошел ли пользователь в систему. (Проблема заключалась в том, что компоненты загружались, а затем проверяли, вошел ли пользователь в систему.) Однако мой вопрос - это стандартная практика? Я не вижу в слишком многих местах
Джессика
1
да, это довольно часто; также упоминается в официальных документах React - просто в меньшем тексте из-за последствий двойного рендеринга DOM для запуска логики до того, как пользователь заметит.
rob2d
На самом деле он запускается после рендеринга компонента. Так что это полностью отличается от componentWillMount.
Иржи Михал
7

Вот как я симулирую конструктор в функциональных компонентах, используя useRefловушку:

function Component(props) {
    const willMount = useRef(true);
    if (willMount.current) {
        console.log('This runs only once before rendering the component.');
        willMount.current = false;        
    }

    return (<h1>Meow world!</h1>);
}

Вот пример жизненного цикла:

function RenderLog(props) {
    console.log('Render log: ' + props.children);
    return (<>{props.children}</>);
}

function Component(props) {

    console.log('Body');
    const [count, setCount] = useState(0);
    const willMount = useRef(true);

    if (willMount.current) {
        console.log('First time load (it runs only once)');
        setCount(2);
        willMount.current = false;
    } else {
        console.log('Repeated load');
    }

    useEffect(() => {
        console.log('Component did mount (it runs only once)');
        return () => console.log('Component will unmount');
    }, []);

    useEffect(() => {
        console.log('Component did update');
    });

    useEffect(() => {
        console.log('Component will receive props');
    }, [count]);


    return (
        <>
        <h1>{count}</h1>
        <RenderLog>{count}</RenderLog>
        </>
    );
}
[Log] Body
[Log] First time load (it runs only once)
[Log] Body
[Log] Repeated load
[Log] Render log: 2
[Log] Component did mount (it runs only once)
[Log] Component did update
[Log] Component will receive props

Конечно, у компонентов класса нет Bodyшагов, невозможно выполнить симуляцию 1: 1 из-за различных концепций функций и классов.

Иржи Михал
источник
Я не углублялся в ваш пример, но ваш первый фрагмент кода работает для меня, спасибо!
Сандрий
6

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

useBeforeFirstRender.js

import { useState, useEffect } from 'react'

export default (fun) => {
  const [hasRendered, setHasRendered] = useState(false)

  useEffect(() => setHasRendered(true), [hasRendered])

  if (!hasRendered) {
    fun()
  }
}

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

import React, { useEffect } from 'react'
import useBeforeFirstRender from '../hooks/useBeforeFirstRender'


export default () => { 
  useBeforeFirstRender(() => {
    console.log('Do stuff here')
  })

  return (
    <div>
      My component
    </div>
  )
}
Небольшое время
источник
3

Есть хороший обходной путь для реализации componentDidMountи componentWillUnmountс useEffect.

Основываясь на документации, useEffectможно вернуть функцию «очистки». эта функция не будет вызываться при первом useEffectвызове, только при последующих вызовах.

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

useEffect(() => {
    console.log('componentDidMount');

    return () => {
        console.log('componentWillUnmount');
    };
}, []);

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

Надеюсь это поможет.

AfikDeri
источник
2
Как это помогает, если это не имеет никакого отношения к componentWillMount ? Я что-то упускаю?
ZenVentzi
Да, вы упускаете тот факт, что при одном и том же useEffectвызове вы получаете ту же функциональность componentWillMountи componentWillUnmountв приятной и чистой форме
AfikDeri
Это не так, useEffectзапускается только после рендеринга, а componentWillMountзапускается до рендеринга компонента.
Перекодировать
@ Оверкод, о котором я говорил, componentDidMountнет componentWillMount. Я упустил это в вопросе, мой плохой.
AfikDeri
1

Вы можете взломать хук useMemo для имитации события жизненного цикла componentWillMount. Просто сделать:

const Component = () => {
   useMemo(() => {
     // componentWillMount events
   },[]);
   useEffect(() => {
     // componentWillMount events
     return () => {
       // componentWillUnmount events
     }
   }, []);
};

Вам нужно будет сохранить ловушку useMemo, прежде чем что-либо, взаимодействующее с вашим состоянием. Это не так, как задумано, но у меня сработало для всех проблем componentWillMount.

Это работает, потому что useMemo не требует фактического возврата значения, и вам не нужно фактически использовать его как что-либо, но поскольку оно запоминает значение, основанное на зависимостях, которые будут выполняться только один раз ("[]"), и оно поверх нашего компонента, оно запускается один раз, когда компонент монтируется раньше всего.

jpmarks
источник
0

https://reactjs.org/docs/hooks-reference.html#usememo

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


источник
usememo для оптимизации производительности. Крюк будет визуализирован снова после того, как он уже смонтирован, если пропеллер сменится, что противоречит цели автора.
max54
0

Ответ Бена Карпа мне кажется верным.

Но поскольку мы используем функциональные способы, от закрытия и HoC может выиграть еще один подход:

const InjectWillmount = function(Node, willMountCallback) {
  let isCalled = true;
  return function() {
    if (isCalled) {
      willMountCallback();
      isCalled = false;
    }
    return Node;
  };
};

Тогда используйте это:

const YourNewComponent = InjectWillmount(<YourComponent />, () => {
  console.log("your pre-mount logic here");
});
Керем Атам
источник
0

Короткий ответ на ваш оригинальный вопрос, как componentWillMountможно использовать с React Hooks:

componentWillMountявляется устаревшим и считается наследием . Реакция рекомендации :

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

Теперь в Hook FAQ вы узнаете, что такое эквивалент конструктора класса для компонентов функций:

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

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

const MyComp = () => {
  const [state, setState] = useState(42) // set initial value directly in useState 
  const [state2, setState2] = useState(createInitVal) // call complex computation

  return <div>{state},{state2}</div>
};

const createInitVal = () => { /* ... complex computation or other logic */ return 42; };
ford04
источник
0

Просто добавьте пустой массив зависимостей в useEffect, в котором он будет работать componentDidMount.

useEffect(() => {
  // Your code here
  console.log("componentDidMount")
}, []);
кодирование
источник
0

Есть простой трюк для симуляции componentDidMountи componentWillUnmountиспользования useEffect:

useEffect(() => {
  console.log("componentDidMount");

  return () => {
    console.log("componentWillUnmount");
  };
}, []);
AmerllicA
источник