940 文字
5 分
Ref callbackとcleanup functions
Ref callback
React では、ref
属性に関数を渡すことが出来る。
<div ref={(node) => { console.log(node); }}/>
これを利用することで、DOM 要素がマウントされたときに何かしらの処理を行ったり、DOM 要素を参照することが出来る。
https://ja.react.dev/reference/react-dom/components/common#ref-callback
Ref callback の cleanup 関数
React v19 で、ref
コールバック関数が返す関数をクリーンアップ関数として扱うようになった。
const ref = (node: HTMLDivElement | null) => { if (node) { console.log("Mounted"); return () => { console.log("Unmounted"); }; }};
クリーンアップ関数は、対象の DOM ノードがアンマウントされたときに呼び出される。
Ref callback と useEffect
useEffect
はuseRef
などと組み合わせることでに似たようなことが出来る。
特に React 19 以降では、クリーンアップ関数の追加により、ほぼ完全に同等のことが出来るようになった。
ただし、useEffect
が React ライフサイクルに依存しているのに対して、ref
コールバックは DOM ノードのライフサイクルに依存しており、完全に同じになることはない。
React のライフサイクルに沿って DOM ノードを処理したい場合、例えば複数の DOM ノードを組み合わせたい場合などは、useEffect
が適している可能性がある。一方で、多くのケースでは DOM ノードと React のライフサイクルの関心を分離できるref
コールバックの方が、例えばイベントの登録や解除の漏れが発生しにくく、適しているように思う。
Ref callback の例
Ref callback を利用した例、特にクリーンアップ関数を利用したものを意図的に多くの載せている。
要素がマウントされたらフォーカスさせる
<input ref={(node) => { node?.focus(); }}/>
要素のサイズを取得する
const [width, setWidth] = useState(0);const masureRef = (node) => { consrt observer = new ResizeObserver((entries) => { setWidth(entries[0].contentRect.width); }); observer.observe(node);
return () => { observer.disconnect(); };}
return <div ref={masureRef}>Width: {width}</div>;
要素がビューポート内に入ったか監視する
const [isVisible, setIsVisible] = useState(false);const ref = (node: HTMLDivElement | null) => { if (node) { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setIsVisible(true); } }); observer.observe(node);
return () => observer.disconnect(); }};return <div ref={ref}>{isVisible ? "Visible" : "Not visible"}</div>;
要素の変更を監視する
const [text, setText] = useState("Initial Text");
const ref = (node: HTMLDivElement | null) => { if (node) { const observer = new MutationObserver(() => { console.log("DOM changed:", node.textContent); });
observer.observe(node, { childList: true, subtree: true });
return () => observer.disconnect(); }};return ( <div ref={ref} contentEditable> {text} </div>);
要素を全画面表示する
const ref = (node: HTMLDivElement | null) => { const controller = new AbortController(); if (node) { const enterFullscreen = () => { node.requestFullscreen?.(); };
node.addEventListener("click", enterFullscreen, { signal: controller.signal, });
return () => controller.abort(); }};
return ( <div ref={ref} style={{ width: "300px", height: "200px", cursor: "pointer", }} > Click to go fullscreen! </div>);
クリップボードにコピーする
const ref = (node: HTMLButtonElement | null) => { if (node) { const controller = new AbortController(); const handleCopy = () => { navigator.clipboard.writeText(node.textContent || ""); };
node.addEventListener("click", handleCopy, { signal: controller.signal, });
return () => controller.abort(); }};
return <button ref={ref}>Copy to Clipboard</button>;
ドラッグ&ドロップを監視する
const ref = (node: HTMLDivElement | null) => { const controller = new AbortController(); if (node) { const handleDragStart = (event: DragEvent) => { event.dataTransfer?.setData("text/plain", "Dragged Content"); };
node.draggable = true; node.addEventListener("dragstart", handleDragStart, { signal: controller.signal, });
return () => controller.abort(); }};
return ( <div ref={ref} style={{ cursor: "move", }} > Drag Me </div>);
カメラの映像を表示する
const ref = (node: HTMLVideoElement | null) => { if (node) { navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => { node.srcObject = stream; });
return () => { if (node.srcObject instanceof MediaStream) { node.srcObject.getTracks().forEach((track) => track.stop()); } }; }};
return <video ref={ref} autoPlay />;
要素のリサイズを監視する
const [size, setSize] = useState({ width: 0, height: 0 });
const ref = (node: HTMLDivElement | null) => { if (node) { const observer = new ResizeObserver(([entry]) => { const { width, height } = entry.contentRect; setSize({ width, height }); });
observer.observe(node);
return () => observer.disconnect(); }};
return ( <div> <div ref={ref} style={{ width: "200px", height: "200px", resize: "both", overflow: "auto", }} /> <p> Width: {size.width}px, Height: {size.height}px </p> </div>);
要素をアニメーションさせる
const ref = (node: HTMLDivElement | null) => { if (node) { const animation = node.animate( [{ transform: "translateX(0)" }, { transform: "translateX(100px)" }], { duration: 1000, iterations: Infinity } );
return () => animation.cancel(); }};
return <div ref={ref}>Auto moving</div>;
Ref callbackとcleanup functions
https://blog.ohirunewani.com/posts/react-ref-callback-patterns/