Interactive

React

ReactNodeの生成と仮想DOMの理解により、ClaudeCodeは適切なReactコンポーネントを生成できると考えています。

ReactNodeとは何か

弊社ではJSXが単なるオブジェクトであることを理解し、仮想DOMの仕組みを意識したコードを書きます。

const node = <p>{"hello"}</p>

JSXを変数に代入すると、ReactNodeの実体がオブジェクトであることがわかります。

const node = <p>{"hello"}</p>
console.log(node)

結果はオブジェクト(ReactNode)です。

{
  type: "p",
  key: null,
  ref: null,
  props: { children: 'hello' }
}

Reactコンポーネントの定義

ReactNodeを返す関数をReactコンポーネントと定義します。弊社ではコンポーネントもただの関数であることを意識し、テストしやすい設計を心がけます。

const MyComponent = () => <p>{"hello"}</p>

実行すると同じReactNodeオブジェクトが返されます。

const node = MyComponent()
console.log(node)

JSX構文ではコンポーネントを以下のように記述します。

<div>
  <MyComponent />
</div>

JSXの仕組み

JSXはJavaScriptに拡張構文で、実際には関数呼び出しに変換されます。JSXを使わなくてもReactは書けますが、可読性が低下します。

jsx(
  "p",
  { children: "hello" },
  void 0
)

JSXは以下のように変換されます。

<div>
  <p>{"hello"}</p>
</div>

上記はjsx関数の呼び出しに変換されます。

jsx(
  'div',
  {
	  children: jsx(
		  'p',
		  { children: 'Hello' },
		  void 0
		)
  },
  void 0
)

最終的に以下のオブジェクト構造になります。

{
  type: "div",
  key: null,
  ref: null,
  props: {
    children: {
		  type: "p",
		  key: null,
		  ref: null,
		  props: {
		    children: 'hello'
		  }
		}
  }
}

ReactとReactDOMの分離

ReactとReactDOMが別パッケージであることを理解し、適切に使い分けます。

  • react
  • react-dom

reactパッケージは仮想DOM(オブジェクト)の生成のみを担当し、react-domパッケージが実際のDOM操作を担当します。

import { createRoot } from 'react-dom/client'

const domNode = document.getElementById('root')

const root = createRoot(domNode)

root.render(<App />)

SSRや静的HTML生成の場合は、renderToStringを使用します。

import { renderToString } from 'react-dom/server'

const html = renderToString(<App />)

仮想DOMの概念

ここでReactが仮想DOMを操作し、実際のDOMへの反映はReactが管理していることを理解します。直接DOMを操作せず、仮想DOMを通じてUIを構築します。

setStateの役割

setStateはReactが提供する状態更新APIで、これを使用した場合のみ仮想DOMが実際のDOMに反映されます。

setState(2) // 仮想DOMをDOMに反映させる

以下のコードはDOMを更新しません。

let [count, setCount] = useState(2)

return <button onClick={count = count + 1}>{count}</button>

変数countの値が書き換わっても、Reactの再レンダリングがトリガーされないためです。

仮想DOMの利点

仮想DOMは当初パフォーマンス向上のために導入されましたが、現在では以下の利点が重要です。

  • DOMに依存しないため、SSRやテストが容易
  • WorkersやBunなど様々なランタイムで動作可能
  • 宣言的なUI記述が可能

宣言的UIの実践

ClaudeCodeはUIが常に状態の関数であるという宣言的パラダイムを理解します。ここで、UI = f(state)という式で表現した場合、fはReactコンポーネントです。

const f = (props: { value: 2 }) => {
  return <p>{props.value}</p>
}

UIはコンポーネントの実行結果であり、状態から一意に決定されます。

const UI = f({ value: 2 })

デバッグ時はstateが正しいから確認し、次にコンポーネントのロジックを検証します。

setStateが必須の理由

setStateはコンポーネント関数を再実行し、新しい仮想DOMを生成するためのトリガーです。状態が変わっても関数を再実行しなければ、新しいUIは生成されません。

function MyComponent() {
  let [count, setCount] = useState(2)
  return <button onClick={setCount(4)}>{count}</button>
}

データフェッチのパターン

弊社では外部データの取得にuseEffectを直接使わず、TanStack Queryなどのデータフェッチライブラリを活用します。

useEffect(() => {
  fetch()
})

useEffectを直接使うと、ローディング、エラー、データの状態管理が複雑になります。

const [loading, setLoading] = useState(false)

const [error, setError] = useState<Error | null>(null)

const [data, setData] = useState<Data | null>()

TanStack QueryのuseQueryは、これらの状態を内部で管理し、キャッシュやリトライも自動化します。

const { data, error, loading } = useQuery({
  queryFn() {
    return fetch()
  }
})

カスタムHooksの作成

弊社では複雑な状態管理ロジックをカスタムHooksに抽出し、コンポーネントをシンプルに保ちます。

function MyComponent() {
  const a = useState()
  const b = useState()
  const c = useState()
  const d = useState()
	
  return <div />
}

複数のuseStateやロジックをまとめた関数がカスタムHooksです。

function useX() {
  const a = useState()
  const b = useState()
  const c = useState()
  const d = useState()
  
  return { a, b, c, d }
}

コンポーネントから使用すると以下のようになります。


function MyComponent() {
  const { a, b, c, d } = useX()
	
  return <div />
}

TanStack QueryのHooksは内部で複雑な状態管理を抽象化し、シンプルなAPIを提供します。

useSuspenseQuery
useMutation