LoginSignup
40
10

More than 1 year has passed since last update.

RFCに追加されたuseEventについて

Last updated at Posted at 2022-05-25

React RFCs(request for comments)に登場した新しいhooksのuseEventの長所と使い所をまとめてみましたので、参考にして頂ければ幸いです。

RFCについて

Reactのコアメンバーとコミュニティメンバーが共同で新機能を設計できることを目的としており、誰でも参加可能でCLS(貢献者ライセンス契約)受理後にマークダウンファイルPRが提出できるようになります。
PRがマージされるとRFCは「アクティブ」となり、最終的にReactに取り込むことを目標に実装することができます。
詳しくはこちらのREADMEを参照

hooksにおける課題(コールバック関数編)

親コンポーネントから子コンポーネントにpropsでコールバック関数を渡すシーンにおいて以下のようなコードがあります。

Chat.tsx
const Chat = () => {
  const [text, setText] = useState('')
  const onClick = () => {
    sendMessage(text)
  }

  return <SendButton onClick={onClick} />
}

このコードの問題点はテキストが変更されるたびに、onClick関数が再生成されてしまうことで、SendButtonが毎回再レンダリングされてしまいます。

useCallbackを使って解決しようとしたパターン

Chat.tsx
const Chat = () => {
  const [text, setText] = useState('')
  const onClick = useCallback(() => {
    sendMessage(text)
  }, [text])

  return <SendButton onClick={onClick} />
}

関数が再生成されない限り再レンダリングが走らないようになりましたが、テキストが変わるたびにonClick関数が再生成されて、SendButtonが毎回再レンダリングされます。

既存のhooksを使った解決策

Chat.tsx
const Chat = () => {
  const [text] = useState('')
  const sendRef = useRef(null)
  useLayoutEffect(() => {
    sendRef.current = () => sendMessage(text)
  })

  const onClick = useCallback((...args) => {
    return sendRef.current(...args)
  }, [])

  return <SendButton onClick={onClick} />
}

かなり可読性が悪いですよね^^;

useEventで解決するパターン

Chat.tsx
const Chat = () => {
  const [text, setText] = useState('')
  const onClick = useEvent(() => {
    sendMessage(text)
  })

  return <SendButton onClick={onClick} />
}

useEvent()を使えば、関数内で使われるプロップやステートが変わっても、同じ参照整合性を維持することができます。

hooksにおける課題(useEffect編)

次に、ページが更新した際にURLとユーザー名のログを残す実装でuseEffectを使った例です。

Page.tsx
const Page = () => {
  useEffect(() => {
    logAnalytics('visit_page', route.url, currentUser.name)
  }, [route.url, currentUser.name])
}

こちらの問題点は、本来であればURLが変わった時のみlogAnalytics()を実行したいのに、ユーザー名が変わったときにも走ってしまいます。currentUser.nameを消しても、ESLintのuseEffect has amissing dependencyの警告が出てしまいます^^;

useCallbackを使って解決しようとしたパターン

Page.tsx
const Page = () => {
  const logVisit = useCallback((url) => {
    logAnalytics('visit_page', url, currentUser.name)
  }, [currentUser.name])
  useEffect(() => {
    logVisit(route.url)
  }, [route.url])
}

これでも、currentUser.nameが変わるたびに関数が再生成される問題は残っています。

useEventで解決するパターン

Page.tsx
const Page = () => {
  const logVisit = useEvent((pageUrl) => {
    logAnalytics('visit_page', pageUrl, currentUser.name)
  })
  useEffect(() => {
    logVisit(route.url)
  }, [route.url])
}

これで、useEffect関数本体からcurrentUser.nameを削除し、route.urlが変更されたときのみlogVisit()が再生成されずに実行することができるようになります。依存配列もなくなりスッキリしますね!

考察

useEventは現状のhooksにおける大きな課題になっている、コールバック関数が再生成されることによる再レンダリングとuseEffectやuseCallback内での無駄な実行を防止し、依存配列の指定に早く公式採用されるのを心待ちにしております。

参考

40
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
10