Closed15

Next.jsでReact Compilerを試してみた

myttymytty

以下を実行して、Nextjs プロジェクトを作成。React Compiler を使用するには、React 19 に対応している必要があるため、canary バージョンで作成する。

npx create-next-app@canary

ちなみに、14 から 15 へアップデートする場合は、以下を実行

npm i next@rc react@rc react-dom@rc eslint-config-next@rc

※ Typescript を使用しているのであれば、@types/react と @types/react-dom も最新バージョンにアップアップデートする必要がある。

myttymytty

以下のような 2つのコンポーネントを作成。(超適当)
Child コンポーネントはメモ化しておく

Parent.tsx
'use client'
import React, { useState, useMemo, useCallback } from 'react'
import { Child } from './Child'

const heavyComputation = (num: number) => {
  console.log('Heavy computation in progress...')
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += num;
  }
  return result;
}

export const Parent = () => {
  console.log('Rendering Parent')
  const [count, setCount] = useState(0)

  const [flag, setFlag] = useState(false)

  const calcResult = heavyComputation(count)

  const handleIncrement = () => {
    setCount((count) => count + 1)
  }

  const handleToggle = () => {
    setFlag(!flag)
  }

  return (
    <div>
      <p>Count: {count}</p>
      <Child handleIncrement={handleIncrement} />
      <p>Calculation Result: {calcResult}</p>
      <button type="button" onClick={handleToggle}>Toggle Flag</button>
      <p>Flag: {flag ? 'True' : 'False'}</p>
    </div>
  )
}
Child.tsx
import React from 'react'

type ChildProps = {
  handleIncrement: () => void
};

export const Child  = React.memo(({ handleIncrement }: ChildProps) => {
  console.log('Rendering Child')

  return (
    <>
      <button type="button" onClick={handleIncrement}>+1</button>
    </>
  )
})

Child.displayName = 'Child'

myttymytty

Toggle ボタンを押すと、calcResult が再計算されるし、Parentコンポーネントがレンダリングされるたびに handleIncrement関数が再生成されるので、 それを Props として受け取る Child コンポーネントも再レンダリングされる。

myttymytty

useMemo と useCallback を利用して、calcResult と handleIncrement をそれぞれメモ化。

const calcResult = useMemo(() => heavyComputation(count), [count])

const handleIncrement = useCallback(() => {
  setCount((count) => count + 1);
 }, [])

myttymytty

メモ化したことで、Toggle ボタンを押しても、Parent コンポーネントが再レンダリングされるのみ。

myttymytty

React Compiler のインストール

npm install babel-plugin-react-compiler
myttymytty

インストールが完了したら、next.config.mjs に reactCompiler オプションを追加する

const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};
 
module.exports = nextConfig;
myttymytty

手動のメモ化は排除する

Parent.tsx
'use client'
import React, { useState, useMemo, useCallback } from 'react'
import { Child } from './Child'

const heavyComputation = (num: number) => {
  console.log('Heavy computation in progress...')
  let result = 0;
  for (let i = 0; i < 1000000000; i++) {
    result += num;
  }
  return result;
}

export const Parent = () => {
  console.log('Rendering Parent')
  const [count, setCount] = useState(0)

  const [flag, setFlag] = useState(false)

  const calcResult = heavyComputation(count)

  const handleIncrement = () => {
    setCount((count) => count + 1)
  }

  const handleToggle = () => {
    setFlag(!flag)
  }

  return (
    <div>
      <p>Count: {count}</p>
      <Child handleIncrement={handleIncrement} />
      <p>Calculation Result: {calcResult}</p>
      <button type="button" onClick={handleToggle}>Toggle Flag</button>
      <p>Flag: {flag ? 'True' : 'False'}</p>
    </div>
  )
}

Child.tsx
import React from 'react'

type ChildProps = {
  handleIncrement: () => void;
}

export const Child  = ({ handleIncrement }: ChildProps) => {
  console.log('Rendering Child')

  return (
    <>
      <button type="button" onClick={handleIncrement}>+1</button>
    </>
  )
}


myttymytty

Toggle ボタンを押しても、calcResult の再計算とChild コンポーネントの再レンダリングは行われない。

myttymytty

object もメモ化されるのか試してみた。
useEffect の依存配列に object を指定することは基本ないと思うが、以下のように object を useEffect の依存配列に含める。

const object = {aa: 'aa', bb: 'bb'}

useEffect(() => {
  console.log('')
}, [object])

本来、object をメモ化していないので、コンポーネントがレンダリングされるたびに、console が実行されるはずだが、実行されなかったので、React Compiler が有効なとき、オブジェクトも自動的にメモ化される。

myttymytty

React Compiler を使用することで、手動のメモ化が必要なくなり、自動的にメモ化されるようになるのはほんまに魅力しかないな。可読性も間違いなく上がって、シンプルで直感的なコードになる。
React の勉強始めたての時に一番つまずいたのがメモ化の部分(useMemo と useCallback の概念)やったから、初学者にとって学習のハードルが下がるんじゃないかな。

このスクラップは2024/05/30にクローズされました