Jak używać componentWillMount () w React hookach?

175

W oficjalnych dokumentach React wspomina:

Jeśli znasz metody cyklu życia klasy React, możesz pomyśleć o hooku useEffect jako połączeniu componentDidMount, componentDidUpdate i componentWillUnmount.

Moje pytanie brzmi - jak możemy wykorzystać componentWillMount()metodę cyklu życia w hooku?

Abrar
źródło

Odpowiedzi:

337

Nie można używać żadnego z istniejących metod cyklu życia ( componentDidMount, componentDidUpdate, componentWillUnmountitd) w haku. Mogą być używane tylko w komponentach klasy. A z hakami można używać tylko w elementach funkcjonalnych. Poniższy wiersz pochodzi z dokumentu React:

Jeśli jesteś zaznajomiony z React metod klasy cyklu życia, można myśleć useEffectHook jak componentDidMount, componentDidUpdatei componentWillUnmountłączone.

Sugeruje to, że możesz naśladować tę metodę cyklu życia z komponentu klasy w komponentach funkcjonalnych.

Kod wewnątrz jest componentDidMounturuchamiany raz po zamontowaniu komponentu. useEffectodpowiednikiem haka dla tego zachowania jest

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

Zwróć uwagę na drugi parametr (pusta tablica). To będzie działać tylko raz.

Bez drugiego parametruuseEffect hak zostanie wywołana na każdy render składnika, które mogą być niebezpieczne.

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

componentWillUnmountsłuży do czyszczenia (np. usuwanie detektorów zdarzeń, anulowanie licznika czasu itp.). Załóżmy, że dodajesz odbiornik zdarzeń componentDidMounti usuwasz go, componentWillUnmountjak poniżej.

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

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

Hook odpowiednik powyższego kodu będzie następujący

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

  // returned function will be called on component unmount 
  return () => {
    window.removeEventListener('mousemove', () => {})
  }
}, [])
Bhaskar Gyan Vardhan
źródło
183
Ładne wyjaśnienie innych zdarzeń cyklu życia, ale nie dotyczy to konkretnie alternatywy dla componentWillMount ().
Shiraz
3
W przykładach PanResponder widziałem, że komponent componentWillMount wydaje się być wymagany, w przeciwnym razie otrzymasz niezdefiniowane panHandlers.
Dror Bar
2
Teraz naprawdę rozumiem tę useEffect()funkcję, dzięki.
Iharob Al Asimi
67
Dlaczego jest to akceptowana odpowiedź? Nie wspomniałeś o ekwiwalencie haka dlacomponentWillMount
Mykybo
3
@techexpert Pytanie dotyczyło odpowiednika componentWillMount, nie componentWillUnmount. Ta odpowiedź rzeczywiście w ogóle nie odpowiada na pytanie, a jedynie powtórzyła to, co PO już sugerował, aby wiedzieć.
JoshuaCWebDeveloper
62

useComponentDidMount hook

W większości przypadków useComponentDidMountjest to narzędzie, którego należy użyć. Uruchomi się tylko raz, po zamontowaniu komponentu (wstępnym renderowaniu).

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

useComponentWillMount

Należy zauważyć, że w klasach komponenty componentWillMountsą uważane za starsze. Jeśli chcesz, aby kod działał tylko raz przed zamontowaniem komponentu, możesz użyć konstruktora. Więcej o tym tutaj . Ponieważ komponent funkcjonalny nie ma odpowiednika konstruktora, użycie haka do uruchomienia kodu tylko raz przed zamontowaniem komponentu może mieć sens w niektórych przypadkach. Możesz to osiągnąć dzięki niestandardowemu haczykowi.

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

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

  willMount.current = false;
};

Istnieje jednak pułapka. Nie używaj go do asynchronicznego ustawiania swojego stanu (np. Po żądaniu serwera. Jak można się spodziewać, wpłynie to na początkowe renderowanie, którego nie będzie). W takich przypadkach należy się zająć useComponentDidMount.

Próbny

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

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

Pełne demo

Ben Carp
źródło
18
To jedyna odpowiedź, która odpowiada na to pytanie i ma sens. Dziękuję Ci!
chumakoff
3
Jedynym problemem jest to, że otrzymujesz dodatkowe renderowanie ze względu na aktualizację stanu. Używając zamiast tego ref, uzyskujesz pożądane zachowanie bez dodatkowego renderowania: `const useComponentWillMount = func => {const willMount = useRef (true); useEffect (() => {willMount.current = false;}, []); if (willMount.current) {func (); }}; `
remiks 23
2
Ta funkcjonalna implementacja w componentWillMountoparciu o useEffectma dwa problemy. Po pierwsze, nie ma cyklu życia komponentów funkcjonalnych, oba zaczepy będą działać po wyrenderowaniu komponentu, więc Runs only once before component mountsjest to mylące. Drugi componentWillMountjest wywoływany podczas renderowania na serwerze i useEffectnie jest. Wiele bibliotek nadal polega na tym, UNSAFE_componentWillMountponieważ obecnie jest to jedyny sposób na wywołanie efektu ubocznego po stronie serwera.
Paolo Moretti
2
@PaoloMoretti, dzięki. Ten hak componentWillMount nie jest dokładnym odpowiednikiem cyklu życia componentWillMount w składniku klasy. Jednak funkcja, która jest do niego przekazywana, zostanie uruchomiona natychmiast, tylko przy pierwszym wywołaniu. W praktyce oznacza to, że będzie działać przed renderowaniem, a nawet zanim zwróci wartość po raz pierwszy. Czy możemy się z tym zgodzić? Zgadzam się, że użycie nazwy componentWillMount nie jest idealne, ponieważ ta nazwa ma określone znaczenie z wersji cyklu życia klasy. Może lepiej nazwałbym to „useRunPreMount”.
Ben Carp
1
@PaoloMoretti, nie do końca rozumiem. Nie pracuję z SSR, ale zobowiązuję się, że na komponencie SSR, WillMount działa dwukrotnie - raz na serwerze i raz na kliencie. Myślę, że to samo dotyczy wywołania zwrotnego, które jest przekazywane do useComponentDidMount. useComponentDidMount przekazuje informacje o funkcji useEffect, aby zatrzymać wywoływanie wywołania zwrotnego. Dopóki callback useEffect nie zostanie wykonany, funkcja komponentu będzie działać dwukrotnie - raz na serwerze i raz na kliencie. Czy tak nie jest?
Ben Carp
53

Jak podaje reactjs.org, w przyszłości składnik componentWillMount nie będzie obsługiwany. https://reactjs.org/docs/react-component.html#unsafe_componentwillmount

Nie ma potrzeby używania elementu componentWillMount.

Jeśli chcesz coś zrobić przed zamontowaniem komponentu, po prostu zrób to w konstruktorze ().

Jeśli chcesz wykonywać żądania sieciowe, nie rób tego w componentWillMount. Dzieje się tak, ponieważ spowoduje to nieoczekiwane błędy.

Żądania sieciowe można wykonać w module componentDidMount.

Mam nadzieję, że to pomoże.


zaktualizowano 08.03.2019 r

Powodem, dla którego pytasz o componentWillMount, jest prawdopodobnie to, że chcesz zainicjować stan przed renderowaniem.

Po prostu zrób to w useState.

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

a może chcesz uruchomić funkcję w componentWillMount, na przykład, jeśli twój oryginalny kod wygląda tak:

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

z hakiem wszystko, co musisz zrobić, to usunąć metodę cyklu życia:

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

Chcę tylko dodać coś do pierwszej odpowiedzi na temat useEffect.

useEffect(()=>{})

useEffect działa na każdym renderowaniu, jest połączeniem składników componentDidUpdate, componentDidMount i ComponentWillUnmount.

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

Jeśli dodamy pustą tablicę w useEffect, będzie ona działać tylko wtedy, gdy komponent zostanie zamontowany. Dzieje się tak, ponieważ useEffect porówna przekazaną do niego tablicę. Więc nie musi to być pusta tablica, może to być tablica, która się nie zmienia. Na przykład może to być [1,2,3] lub ['1,2']. useEffect nadal działa tylko po zamontowaniu komponentu.

To zależy od Ciebie, czy chcesz, aby był uruchamiany tylko raz, czy po każdym renderowaniu. Nie jest niebezpieczne, jeśli zapomnisz dodać tablicę, o ile wiesz, co robisz.

Stworzyłem próbkę do haka. Proszę sprawdź to.

https://codesandbox.io/s/kw6xj153wr


aktualizacja 21.08.2019 r

Odkąd napisałem powyższą odpowiedź była biała. Myślę, że jest coś, na co musisz zwrócić uwagę. Kiedy używasz

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

Kiedy reakcja porównuje wartości przekazane do tablicy [], używa Object.is () do porównania. Jeśli przekażesz mu obiekt, taki jak

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

To jest dokładnie to samo, co:

useEffect(()=>{})

Wyrenderuje się ponownie za każdym razem, ponieważ gdy Object.is () porównuje obiekt, porównuje jego odwołanie, a nie samą wartość. To jest to samo, dlaczego {} === {} zwraca fałsz, ponieważ ich odwołania są różne. Jeśli nadal chcesz porównać sam obiekt, a nie odniesienie, możesz zrobić coś takiego:

useEffect(()=>{},[JSON.stringify({name:'Tom'})])
MING WU
źródło
17
a pytanie brzmiało, jak to zaimplementować z haczykami
Shubham Khatri
3
ale nie musisz implementować go z hakami, ponieważ nie będzie obsługiwany. Nie musisz się uczyć, jak to zrobić z haczykami.
MING WU
1
Teraz, gdy wspomniałeś, że komponent componentDidMount jest prawidłowym cyklem życia do użycia, mógłbyś dodać, jak zaimplementować to w swojej odpowiedzi, a wtedy twoja odpowiedź miałaby więcej sensu niż zaakceptowana odpowiedź
Shubham Khatri
8
Z pewnością powinna to być akceptowana odpowiedź - wyjaśnia, że ​​ComponentWillMount nie jest dostępny w paradygmacie hooków. Inicjalizacja w komponentach funkcjonalnych jest uproszczona - po prostu musi być częścią funkcji
Shiraz
1
Jak to jest to samo, co componentWillMount? Jeśli wrzucisz kod do komponentu funkcjonalnego, będzie on uruchamiany przy każdym renderowaniu, nie tylko wtedy, gdy komponent ma zostać zamontowany.
Overcode
13

useLayoutEffectmożna to osiągnąć z pustym zestawem obserwatorów ( []), jeśli funkcjonalność jest w rzeczywistości podobna do componentWillMount- będzie działać, zanim pierwsza treść dotrze do DOM - chociaż faktycznie są dwie aktualizacje, ale są one synchroniczne przed rysowaniem na ekranie.

na przykład:


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

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

Korzyść w porównaniu useStatez inicjatorem / ustawiaczem lub useEffectpolega na tym, że może obliczyć przepustkę renderowania, nie ma rzeczywistych ponownych renderów do DOM, które użytkownik zauważy, i jest uruchamiany przed pierwszym zauważalnym renderowaniem, co nie ma miejsca w przypadku useEffect. Wadą jest oczywiście niewielkie opóźnienie w pierwszym renderowaniu, ponieważ przed malowaniem na ekran musi nastąpić sprawdzenie / aktualizacja. Jednak to naprawdę zależy od twojego przypadku użycia.

Osobiście uważam, że useMemojest w porządku w niektórych niszowych przypadkach, w których musisz zrobić coś ciężkiego - o ile pamiętasz, że jest to wyjątek od normy.

rob2d
źródło
3
useLayoutEffect to droga do zrobienia !!!! To odpowiedź na moje pytanie dotyczące sprawdzania, czy użytkownik jest zalogowany. (Problem polegał na tym, że komponenty ładowały się, a następnie sprawdzały, czy użytkownik jest zalogowany). Moje pytanie brzmi jednak, czy to standardowa praktyka? Nie widzę w zbyt wielu miejscach
Jessica
1
tak, to dość powszechne; wspomniane również w oficjalnych dokumentach Reacta - tylko w mniejszym tekście ze względu na konsekwencje renderowania podwójnego DOM w celu uruchomienia logiki, zanim użytkownik zauważy.
rob2d
W rzeczywistości działa po wyrenderowaniu komponentu. Więc jest zupełnie inny niż componentWillMount.
Jiri Mihal
7

Oto sposób, w jaki symuluję konstruktora w komponentach funkcjonalnych za pomocą useRefhooka:

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>);
}

Oto przykład cyklu życia:

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

Oczywiście komponenty klas nie mają Bodykroków, nie jest możliwe wykonanie symulacji 1: 1 ze względu na różne koncepcje funkcji i klas.

Jiri Mihal
źródło
Nie zagłębiłem się w twój przykład, ale twój pierwszy fragment kodu działa dla mnie, dziękuję!
SAndriy
6

Napisałem niestandardowy punkt zaczepienia, który uruchomi funkcję raz przed pierwszym renderowaniem.

useBeforeFirstRender.js

import { useState, useEffect } from 'react'

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

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

  if (!hasRendered) {
    fun()
  }
}

Stosowanie:

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


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

  return (
    <div>
      My component
    </div>
  )
}
Mało czasu
źródło
3

Jest ładny obejście do wdrożenia componentDidMounti componentWillUnmountz useEffect.

Na podstawie dokumentacji useEffectmoże zwrócić funkcję „czyszczenia”. ta funkcja nie będzie wywoływana przy pierwszym useEffectwywołaniu, tylko przy kolejnych.

Dlatego jeśli użyjemy useEffectzaczepu bez żadnych zależności, zaczep zostanie wywołany tylko wtedy, gdy komponent jest zamontowany, a funkcja "czyszczenia" jest wywoływana, gdy komponent jest odmontowywany.

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

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

Wywołanie funkcji powrotu oczyszczania jest wywoływane tylko wtedy, gdy komponent jest odmontowany.

Mam nadzieję że to pomoże.

AfikDeri
źródło
2
Jak to pomoże, jeśli nie ma to nic wspólnego z componentWillMount ? Czy coś mi brakuje?
ZenVentzi
Tak, brakuje fakt, że w tej samej useEffectrozmowy można dostać taką samą funkcjonalność componentWillMounti componentWillUnmountw ładnym i czystym sposób
AfikDeri
To nieprawda, useEffectdziała tylko po renderowaniu, podczas gdy componentWillMountdziała przed renderowaniem składnika.
Overcode
@Overcode, o którym mówiłem, componentDidMountnie componentWillMount. Brakowało mi tego w pytaniu, moja wina.
AfikDeri
1

Możesz zhakować hak useMemo, aby naśladować zdarzenie cyklu życia componentWillMount. Po prostu zrób:

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

Musiałbyś zachować punkt zaczepienia useMemo, zanim cokolwiek wchodzi w interakcję z twoim stanem. Nie jest to zamierzone, ale działało w przypadku wszystkich problemów z komponentem WillMount.

To działa, ponieważ useMemo nie wymaga zwracania wartości i nie musisz jej używać jako czegokolwiek, ale ponieważ zapamiętuje wartość na podstawie zależności, które będą uruchamiane tylko raz ("[]") i są na wierzchu naszego komponentu uruchamia się raz, gdy komponent zostanie zamontowany przed czymkolwiek innym.

jpmarks
źródło
0

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

Pamiętaj, że funkcja przekazana do useMemo działa podczas renderowania. Nie rób tam niczego, czego normalnie nie zrobiłbyś podczas renderowania. Na przykład efekty uboczne należą do useEffect, a nie useMemo.


źródło
usememo służy do optymalizacji wydajności. Hak zostanie ponownie wyrenderowany po zamontowaniu już w przypadku zmiany rekwizytu, co zniweczy cel autora.
max54
0

Odpowiedź Bena Karpa wydaje mi się jedyną słuszną.

Ale ponieważ używamy funkcjonalnych sposobów, tylko inne podejście może przynieść korzyści z zamknięcia i HoC:

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

Następnie użyj go:

const YourNewComponent = InjectWillmount(<YourComponent />, () => {
  console.log("your pre-mount logic here");
});
Kerem atam
źródło
0

Krótka odpowiedź na Twoje pierwotne pytanie, w jaki sposób componentWillMountmożna ich używać z hakami React:

componentWillMountjest przestarzała i uważane dziedzictwo . Reaguj rekomendację :

Generalnie zamiast tego do inicjalizacji stanu zalecamy użycie konstruktora ().

Teraz w Hook FAQ dowiesz się, jaki jest odpowiednik konstruktora klasy dla komponentów funkcji:

konstruktor: komponenty funkcji nie potrzebują konstruktora. Stan można zainicjować w wywołaniu useState. Jeśli obliczenie stanu początkowego jest kosztowne, możesz przekazać funkcję do useState.

Tak więc przykład użycia componentWillMountwygląda następująco:

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
źródło
0

Po prostu dodaj pustą tablicę zależności w useEffect, będzie działać jak componentDidMount.

useEffect(() => {
  // Your code here
  console.log("componentDidMount")
}, []);
Kodowanie
źródło
0

Jest prosta sztuczka, aby zasymulować componentDidMounti componentWillUnmountużywając useEffect:

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

  return () => {
    console.log("componentWillUnmount");
  };
}, []);
AmerllicA
źródło