Closed17

useEffectのdocsを読む

hajimismhajimism

Reference

useEffect(setup, dependencies?)

Parameters

  • setup
    • 実のところ、setupとcleanupに分かれる
    • first render: run setup
    • every re-render with changed dependencies: run cleanup with the old values → run setup with the new values
  • optional dependencies
    • Object.isによる比較をして差分があればre-render時にeffectが発動する
    • dependeiciesが無ければ、every re-renderで発動する
hajimismhajimism

Caveats

EffectはReactの外側のシステムと同期するための窓口。それ以外の用途だと基本使わないよ。
https://react.dev/learn/you-might-not-need-an-effect

If some of your dependencies are objects or functions defined inside the component, there is a risk that they will cause the Effect to re-run more often than needed. To fix this, remove unnecessary object and function dependencies. You can also extract state updates and non-reactive logic outside of your Effect.

dependenciesの設定は慎重に

If your Effect wasn’t caused by an interaction (like a click), React will let the browser paint the updated screen first before running your Effect. If your Effect is doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers), replace useEffect with useLayoutEffect.

user interaction 以外でEffectが発生した場合、effectの前に画面の更新が起こる。これが原因でちらつきなどが発生している場合は、useLayoutEffectを検討する。

あんまりちゃんと使ったこと無いからわからないな。
https://react.dev/reference/react/useLayoutEffect

Even if your Effect was caused by an interaction (like a click), the browser may repaint the screen before processing the state updates inside your Effect. Usually, that’s what you want. However, if you must block the browser from repainting the screen, you need to replace useEffect with useLayoutEffect.

Effect内部でのstateの更新は画面の更新後に行われるらしい。そういう場合でもuseLayoutEffect。

Effects only run on the client. They don’t run during server rendering.

server renderingではEffectは機能しない。

hajimismhajimism

Usage

Connecting to an external system

Some components need to stay connected to the network, some browser API, or a third-party library, while they are displayed on the page. These systems aren’t controlled by React, so they are called external.

サンプルコード

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
  	const connection = createConnection(serverUrl, roomId);
    connection.connect();
  	return () => {
      connection.disconnect();
  	};
  }, [serverUrl, roomId]);
  // ...
}

useEffectの構成

  • external systemとconnectするためのsetup
  • disconnectするためのcleanup
  • それらに関わるdependencies

例えば途中でroomIdが変更すれば、”your Effect will disconnect from the previous room, and connect to the next one.” わかりやすい。

Effectが冪等になるようにsetupにmirrorなcleanupを用意する。

hajimismhajimism

余談だけれども、"The rule of thumb"で「経験則」を意味するらしいことを初めて知った。GPTに軽く由来聞いたら面白いw

The origin of the term "rule of thumb" is debated, but one popular theory is that it comes from an old English law that supposedly allowed a man to beat his wife with a stick as long as the stick was no wider than his thumb. However, it's important to note that this theory is not widely accepted and may not be accurate.

hajimismhajimism

Note

external systemの例

  • A timer managed with setInterval() and clearInterval().
  • An event subscription using window.addEventListener() and window.removeEventListener().
  • A third-party animation library with an API like animation.start() and animation.reset().
hajimismhajimism

Example

JSについて何も知らないのでそもそもIntersectionObserverを知らなかった。とにかく生DOMはReactのexternal systemなのでEffect扱い。

import { useRef, useEffect } from 'react';

export default function Box() {
  const ref = useRef(null);

  useEffect(() => {
    const div = ref.current;
    const observer = new IntersectionObserver(entries => {
      const entry = entries[0];
      if (entry.isIntersecting) {
        document.body.style.backgroundColor = 'black';
        document.body.style.color = 'white';
      } else {
        document.body.style.backgroundColor = 'white';
        document.body.style.color = 'black';
      }
    });
    observer.observe(div, {
      threshold: 1.0
    });
    return () => {
      observer.disconnect();
    }
  }, []);

  return (
    <div ref={ref} style={{
      margin: 20,
      height: 100,
      width: 100,
      border: '2px solid black',
      backgroundColor: 'blue'
    }} />
  );
}

hajimismhajimism

Controlling a non-React widget

refを渡している(DOM側の)MapとpropsのzoomLevelを同期させる例

import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';

export default function Map({ zoomLevel }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    if (mapRef.current === null) {
      mapRef.current = new MapWidget(containerRef.current);
    }

    const map = mapRef.current;
    map.setZoom(zoomLevel);
  }, [zoomLevel]);

  return (
    <div
      style={{ width: 200, height: 200 }}
      ref={containerRef}
    />
  );
}

In this example, a cleanup function is not needed because the MapWidget class manages only the DOM node that was passed to it. After the Map React component is removed from the tree, both the DOM node and the MapWidget class instance will be automatically garbage-collected by the browser JavaScript engine.

hajimismhajimism

Fetching data with Effects

むかしやってたなーこれ笑、って感じだね。もうやらないだろうからスルー。

hajimismhajimism

ライブラリ使うよりuseEffectを使ってdata fetchingを行う方がいい時ってあるのだろうか?どうしてもライブラリを使いたくないとき?

hajimismhajimism

Updating state based on previous state from an Effect

こうじゃなくて

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count + 1); // You want to increment the counter every second...
    }, 1000)
    return () => clearInterval(intervalId);
  }, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
  // ...
}

こう

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(c => c + 1); // ✅ Pass a state updater
    }, 1000);
    return () => clearInterval(intervalId);
  }, []); // ✅ Now count is not a dependency

  return <h1>{count}</h1>;
}

なんか、わかってる人とそうでない人の分水嶺って感じのポイントでいいね

hajimismhajimism

Removing unnecessary object dependencies

Component内部で定義されたObjectはrenderごとに再生性されるため、render間のObject.isで同一になることはない。なのでObjectをdependencies arrayに記述するのは危険。

こうじゃなくて

  const options = { // 🚩 This object is created from scratch on every re-render
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options); // It's used inside the Effect
    connection.connect();
    return () => connection.disconnect();
  }, [options]); // 🚩 As a result, these dependencies are always different on a re-render

こう

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

依存の根本まで遡ろう、みたいな話にも見える

hajimismhajimism

Reading the latest props and state from an Effect

こういうEffectを書いているときに、shoppingCart.lengthに対してはreactiveになりたくないときがある。

  useEffect(() => {
    logVisit(url, shoppingCart.length);
  }, [url, shoppingCart]); // ✅ All dependencies declared

こう書けばいいらしい。useEffectEventはnon-reactiveなcodeを囲い込んだイベントを作るための実験的な機能。

  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, shoppingCart.length)
  });

  useEffect(() => {
    onVisit(url);
  }, [url]); // ✅ All dependencies declared

こっちにも書いてあるらしい。API docsはまだない。
https://react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events

hajimismhajimism

Displaying different content on the server and the client

一部のhydration errorを避けるための書き方

function MyComponent() {
  const [didMount, setDidMount] = useState(false);

  useEffect(() => {
    setDidMount(true);
  }, []);

  if (didMount) {
    // ... return client-only JSX ...
  }  else {
    // ... return initial JSX ...
  }
}
hajimismhajimism

読んでみて

関連ドキュメントも含めると、文章量が多いこと多いこと。「ユーザーがハマるのはここだ!」と見抜いて、丁寧にカバーしに行っている感じがある。useLeyoutEffectとかuseInsertionEffectとかuseEffectEventとか、派生したAPIがこんなにあるのも知らなかった。

一旦useLeyoutEffectは読んでおく。useInsertionEffectはCSS-in-JSライブラリを作るとき用って書いてあるし、useEffectEventはドキュメントがまだ充実してないみたいなのでパス。useEffectEventの考え方難しそう。

このスクラップは2023/07/06にクローズされました