Upgrade to Pro — share decks privately, control downloads, hide ads and more …

useImperativeHandleで理解する クロージャと評価タイミング

useImperativeHandleで理解する クロージャと評価タイミング

Avatar for Tasuku Watanabe

Tasuku Watanabe

January 07, 2026
Tweet

More Decks by Tasuku Watanabe

Other Decks in Programming

Transcript

  1. useImperativeHandleを使ったコンポーネント const VideoPlayer = ({ src, ref }) => {

    const videoRef = useRef<HTMLMediaElement>(null) useImperativeHandle(ref, () => ({ get currentTime() { return videoRef.current?.currentTime // 動画の再生位置 } }), []) return <video ref={videoRef} src={src} /> } DOMへの直接アクセスを隠蔽し、必要な機能だけを公開 2
  2. 親コンポーネントでの使用 const ParentComponent = () => { const parentRef =

    useRef<VideoPlayerRef>(null) const handleClick = () => { parentRef.current?.currentTime // アクセス可能 parentRef.current?.duration // アクセス不可 } return ( <> <VideoPlayer src="video.mp4" ref={parentRef} /> <button onClick={handleClick}>クリック</button> </> ) } 公開するプロパティとメソッドを子コンポーネント側で制限している 3
  3. NG例①:プロパティの値を変数に代入する useImperativeHandle(parentRef, () => { const currentTime = videoRef.current?.currentTime //

    0 const volume = videoRef.current?.volume // 1 return { get currentTime() { return currentTime }, // 0で固定される get volume() { return volume }, // 1で固定される } }, []) 問題: useImperativeHandle実行時に値が評価され、クロージャに固定される → 変数に代入した時点で値がコピーされるため、動画が再生されても更新されない 5
  4. NG例②:DOMへの参照を変数に代入する useImperativeHandle(parentRef, () => { const video = videoRef.current return

    { get currentTime() { return video?.currentTime }, get volume() { return video?.volume }, } }, []) 問題: DOMのマウントが未完了だと、クロージャによって const video = null がキャプチ ャされ、DOMマウント完了後も変数は null のままになる ※ 条件付きレンダリングや遅延マウントなどで発生する可能性 6
  5. 解決策:getter内でrefを直接参照 useImperativeHandle(parentRef, () => ({ get currentTime() { return videoRef.current?.currentTime

    }, get volume() { return videoRef.current?.volume } }), []) 変数に代入せず、getter内で毎回 videoRef.current を参照 → getter呼び出し時に評価されるため、最新の値を取得でき、マウントタイミングに も依存しない 7