Warum wird die Bereinigungsfunktion von „useEffect“ bei jedem Rendern aufgerufen?

Lesezeit: 7 Minuten

Jojis Benutzeravatar
Joji

Ich habe React gelernt und gelesen, dass die Funktion von zurückgegeben wurde useEffect ist für die Bereinigung gedacht und React führt die Bereinigung durch, wenn die Bereitstellung der Komponente aufgehoben wird.

Also habe ich ein wenig damit experimentiert, aber im folgenden Beispiel festgestellt, dass die Funktion jedes Mal aufgerufen wurde, wenn die Komponente erneut gerendert wurde, und nicht nur dann, wenn sie vom DOM getrennt wurde, d console.log("unmount"); jedes Mal, wenn die Komponente erneut gerendert wird.

Warum das?

function Something({ setShow }) {
  const [array, setArray] = useState([]);
  const myRef = useRef(null);

  useEffect(() => {
    const id = setInterval(() => {
      setArray(array.concat("hello"));
    }, 3000);
    myRef.current = id;
    return () => {
      console.log("unmount");
      clearInterval(myRef.current);
    };
  }, [array]);

  const unmount = () => {
    setShow(false);
  };

  return (
    <div>
      {array.map((item, index) => {
        return (
          <p key={index}>
            {Array(index + 1)
              .fill(item)
              .join("")}
          </p>
        );
      })}
      <button onClick={() => unmount()}>close</button>
    </div>
  );
}

function App() {
  const [show, setShow] = useState(true);

  return show ? <Something setShow={setShow} /> : null;
}

Live-Beispiel: https://codesandbox.io/s/vigilant-leavitt-z1jd2

React führt die Bereinigung durch, wenn die Bereitstellung der Komponente aufgehoben wird.

Ich weiß nicht, wo Sie das gelesen haben, aber diese Aussage ist falsch. React führt die Bereinigung durch, wenn sich die Abhängigkeiten zu diesem Hook ändern und der Effekt-Hook erneut mit neuen Werten ausgeführt werden muss. Dieses Verhalten ist beabsichtigt, um die Reaktivität der Ansicht auf sich ändernde Daten aufrechtzuerhalten. Gehen wir vom offiziellen Beispiel aus und nehmen wir an, eine App abonniert Statusaktualisierungen aus dem Profil eines Freundes. Da Sie der große Freund sind, der Sie sind, entscheiden Sie sich, die Freundschaft mit ihm zu beenden und sich mit jemand anderem anzufreunden. Jetzt muss die App die Statusaktualisierungen des vorherigen Freundes abbestellen und auf Aktualisierungen Ihres neuen Freundes hören. Dies ist natürlich und mit der Art und Weise leicht zu erreichen useEffect funktioniert.

 useEffect(() => { 
    chatAPI.subscribe(props.friend.id);

    return () => chatAPI.unsubscribe(props.friend.id);
  }, [ props.friend.id ])

Indem wir die Freundes-ID in die Abhängigkeitsliste aufnehmen, können wir angeben, dass der Hook nur ausgeführt werden muss, wenn sich die Freundes-ID ändert.

In Ihrem Beispiel haben Sie das angegeben array in der Abhängigkeitsliste und Sie ändern das Array in einem festgelegten Intervall. Jedes Mal, wenn Sie das Array ändern, wird der Hook erneut ausgeführt.

Sie können die richtige Funktionalität erreichen, indem Sie einfach das Array aus der Abhängigkeitsliste entfernen und die Callback-Version von verwenden setState Haken. Die Callback-Version arbeitet immer mit der vorherigen Version des Status, sodass der Hook nicht jedes Mal aktualisiert werden muss, wenn sich das Array ändert.

  useEffect(() => {
    const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);

    return () => {
      console.log("unmount");
      clearInterval(id);
    };
  }, []);

Ein zusätzliches Feedback wäre, die ID direkt zu verwenden clearInterval da der Wert geschlossen (erfasst) wird, wenn Sie die Bereinigungsfunktion erstellen. Es ist nicht erforderlich, es unter einer Referenz zu speichern.

  • Dieses Zitat „React führt die Bereinigung durch, wenn die Bereitstellung der Komponente aufgehoben wird.“ kommt direkt von React: reagierenjs.org/docs/hooks-effect.html

    – Mike Portanova

    14. März 2020 um 20:56

  • Lesen Sie weiter, Sie werden sehen, dass dies nicht nur der Fall ist, wenn die Komponente ausgehängt wird. Das ist der Punkt, den ich anspreche.

    – Avin Kavish

    15. März 2020 um 7:49 Uhr


  • Ich muss dies ablehnen, da die von Ihnen zitierte Aussage nicht falsch, sondern lediglich unvollständig ist. Außerdem stammt es aus den offiziellen Dokumenten, sodass es nicht hilfreich ist, zu unterstellen, dass sie etwas Falsches gelesen haben.

    – Steinybot

    31. Juli 2021 um 22:30 Uhr

  • Aus React: „React führt die Bereinigung durch, wenn die Komponente ausgehängt wird. Wie wir jedoch bereits erfahren haben, werden Effekte bei jedem Rendern ausgeführt und nicht nur einmal. Aus diesem Grund bereinigt React auch Effekte aus dem vorherigen Rendering, bevor die Effekte das nächste Mal ausgeführt werden.“ “

    – Christakitos

    19. November 2021 um 10:53 Uhr

Benutzeravatar von nem035
nem035

Die React-Dokumente haben eine Erklärungsabschnitt genau darauf.

Kurz gesagt liegt der Grund darin, dass ein solches Design vor veralteten Daten und Aktualisierungsfehlern schützt.

Der useEffect Hook in React ist so konzipiert, dass er sowohl das erste Rendering als auch alle nachfolgenden Renderings verarbeitet (Hier erfahren Sie mehr darüber).


Effekte werden über ihre Abhängigkeiten gesteuert, nicht über den Lebenszyklus der Komponente, die sie verwendet.

Immer wenn sich Abhängigkeiten eines Effekts ändern, useEffect Bereinigt den vorherigen Effekt und führt den neuen Effekt aus.

Ein solches Design ist vorhersehbarer – Jedes Rendering hat seinen eigenen unabhängigen (reinen) Verhaltenseffekt. Dadurch wird sichergestellt, dass die Benutzeroberfläche immer die richtigen Daten anzeigt (da die Benutzeroberfläche im mentalen Modell von React ein Screenshot des Status für ein bestimmtes Rendering ist).

Die Art und Weise, wie wir Effekte steuern, erfolgt über ihre Abhängigkeiten.

Um zu verhindern, dass die Bereinigung bei jedem Rendern ausgeführt wird, dürfen wir lediglich die Abhängigkeiten des Effekts nicht ändern.

In Ihrem Fall konkret geschieht die Bereinigung, weil array ändert sich, d.h Object.is(oldArray, newArray) === false

useEffect(() => {
  // ...
}, [array]);
//  ^^^^^ you're changing the dependency of the effect

Sie bewirken diese Änderung mit der folgenden Zeile:

useEffect(() => {
  const id = setInterval(() => {
    setArray(array.concat("hello")); // <-- changing the array changes the effect dep
  }, 3000);
  myRef.current = id;

  return () => {
    clearInterval(myRef.current);
  };
}, [array]); // <-- the array is the effect dep

  • Hallo, danke für deine Antwort. Das macht es klar. Aber wenn ich das nicht einstelle array als Abhängigkeit, d. h. ich lasse es als leeres Array, das setInterval wird nur einmal ausgeführt. Wissen Sie, wie man das beheben kann?

    – Joji

    13. Juli 2019 um 23:38 Uhr

  • Nun, das hängt davon ab, wann es laufen soll? Vielleicht setTimeout ist das, wonach Sie suchen?

    – nem035

    13. Juli 2019 um 23:42 Uhr

Vu Nguyens Benutzeravatar
Vu Nguyen

Wie andere gesagt haben, hing der useEffect von den Änderungen des „Arrays“ ab, das im 2. Parameter im useEffect angegeben wurde. Wenn Sie es also auf ein leeres Array setzen, kann dies dazu beitragen, useEffect einmal auszulösen, wenn die Komponente bereitgestellt wird.

Der Trick besteht darin, den vorherigen Zustand des Arrays zu ändern.

setArray((arr) => arr.concat("hello"));

Siehe unten:

  useEffect(() => {
     const id = setInterval(() => {
         setArray((arr) => arr.concat("hello"));
     }, 3000);
     myRef.current = id;
     return () => {
        console.log("unmount");
        clearInterval(myRef.current);
     };
  }, []);

Ich habe Ihre CodeSandbox zur Demonstration geforkt:
https://codesandbox.io/s/heuristic-maxwell-gcuf7?file=/src/index.js

  • ja, richtige Antwort

    – Chirag Joshi

    13. Januar um 7:41 Uhr

Wenn ich mir den Code ansehe, könnte ich vermuten, dass es am zweiten Parameter liegt [array]. Da Sie es aktualisieren, wird ein erneutes Rendern aufgerufen. Versuchen Sie, ein leeres Array festzulegen.

Bei jeder Statusaktualisierung wird ein erneutes Rendern und Unmounten durchgeführt, und dieses Array ändert sich.

Mobeens Benutzeravatar
Mobeen

Es scheint erwartet zu sein. Gemäß der Dokumentation hier, useEffect wird nach dem ersten Rendern, jedem Update und Unmounten aufgerufen.

https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Spitze

Wenn Sie mit den Lebenszyklusmethoden der React-Klasse vertraut sind, können Sie sich useEffect Hook als Kombination von ComponentDidMount, ComponentDidUpdate und ComponentWillUnmount vorstellen.

Benutzeravatar von Archimedes Trajano
Archimedes Trajano

Dies ist ein Scherztest, der die Render- und Effektreihenfolge zeigt.

Wie Sie aus dem Erwarten ersehen können, ist einmal die Abhängigkeit foo Änderungen aufgrund der Statusaktualisierung lösen ein NEUES Rendering aus, gefolgt von der Bereinigungsfunktion des ersten Renderings.

  it("with useEffect async set state and timeout and cleanup", async () => {
    jest.useFakeTimers();
    let theRenderCount = 0;
    const trackFn = jest.fn((label: string) => { });
    function MyComponent() {
      const renderCount = theRenderCount;
      const [foo, setFoo] = useState("foo");
      useEffect(() => {
        trackFn(`useEffect ${renderCount}`);
        (async () => {
          await new Promise<string>((resolve) =>
            setTimeout(() => resolve("bar"), 5000)
          );
          setFoo("bar");
        })();
        return () => trackFn(`useEffect cleanup ${renderCount}`);
      }, [foo]);
      ++theRenderCount;
      trackFn(`render ${renderCount}`);
      return <span data-testid="asdf">{foo}</span>;
    }
    const { unmount } = render(<MyComponent></MyComponent>);
    expect(screen.getByTestId("asdf").textContent).toBe("foo");
    jest.advanceTimersByTime(4999);
    expect(screen.getByTestId("asdf").textContent).toBe("foo");
    jest.advanceTimersByTime(1);
    await waitFor(() =>
      expect(screen.getByTestId("asdf").textContent).toBe("bar")
    );

    trackFn("before unmount");
    unmount();
    expect(trackFn.mock.calls).toEqual([
      ['render 0'],
      ['useEffect 0'],
      ['render 1'],
      ['useEffect cleanup 0'],
      ['useEffect 1'],
      ['before unmount'],
      ['useEffect cleanup 1']
    ])
  });

1451810cookie-checkWarum wird die Bereinigungsfunktion von „useEffect“ bei jedem Rendern aufgerufen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy