useEffectのdocsを読む
これです
useEffect is a React Hook that lets you synchronize a component with an external system.
しょっぱなでこの記事がlinkされている
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で発動する
-
Caveats
EffectはReactの外側のシステムと同期するための窓口。それ以外の用途だと基本使わないよ。
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を検討する。
あんまりちゃんと使ったこと無いからわからないな。
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は機能しない。
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を用意する。
余談だけれども、"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.
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().
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'
}} />
);
}
Wrapping Effects in custom Hooks
カスタムフックについては別途ガイドがあるらしい。ドキュメント充実しすぎ。
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.
Fetching data with Effects
むかしやってたなーこれ笑、って感じだね。もうやらないだろうからスルー。
ライブラリ使うよりuseEffectを使ってdata fetchingを行う方がいい時ってあるのだろうか?どうしてもライブラリを使いたくないとき?
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>;
}
なんか、わかってる人とそうでない人の分水嶺って感じのポイントでいいね
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]);
依存の根本まで遡ろう、みたいな話にも見える
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はまだない。
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 ...
}
}
読んでみて
関連ドキュメントも含めると、文章量が多いこと多いこと。「ユーザーがハマるのはここだ!」と見抜いて、丁寧にカバーしに行っている感じがある。useLeyoutEffect
とかuseInsertionEffect
とかuseEffectEvent
とか、派生したAPIがこんなにあるのも知らなかった。
一旦useLeyoutEffect
は読んでおく。useInsertionEffect
はCSS-in-JSライブラリを作るとき用って書いてあるし、useEffectEvent
はドキュメントがまだ充実してないみたいなのでパス。useEffectEvent
の考え方難しそう。