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