メインコンテンツまでスキップ

TypeScript との連携:型安全なコンポーネント

この章で学ぶこと

Button コンポーネントを支える TypeScript の型を学びます。React 19 では、型の書き方が以前よりシンプルになりました。

なぜ型安全性が重要なのか

型があると、コンポーネントの「使い方」がコードに表現され、間違いを未然に防げます。

型がもたらす 4 つの価値
├─ エディタ支援:自動補完/その場でドキュメント
├─ エラー防止:コンパイル時に検出/タイポを防ぐ
├─ 安全なリファクタ:変更の影響が分かる
└─ 自己文書化:使い方が型で分かる

Button の引数の型

完成形の章の Button は、引数に直接、型を書いていました。

function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
// ...
}

型の部分は 3 つを &(交差型)でつないでいます。それぞれを見ていきましょう。

型に名前を付けたいときは、type で別出しにもできます。

type ButtonProps = React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}

function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
// ...
}

React.ComponentProps<"button">

React.ComponentProps<"button"> は、HTML の <button> が受け取れるすべての propsを表す型です。

// onClick, disabled, type, aria-*, children… そして ref まで含まれる
<Button type="submit" disabled={isLoading} onClick={handleClick} aria-label="送信">
送信
</Button>

これを使えば、onClickdisabled などを自分で 1 つずつ定義する必要はありません。

// ❌ 手で全部書くのは大変
type ButtonProps = {
onClick?: () => void
disabled?: boolean
type?: "submit" | "reset" | "button"
// …何十個も…
}

// ✅ ComponentProps<"button"> ですべてまとめて取り込む
type ButtonProps = React.ComponentProps<"button">
備考

以前は React.ButtonHTMLAttributes<HTMLButtonElement> という型がよく使われていました。React.ComponentProps<"button"> はそれを含み、さらに ref も含むため、より簡潔です。React 19 では ref が通常の props として渡せるので、この型だけで ref 対応が完結します。最新の shadcn/ui もこちらを使っています。

② VariantProps

VariantProps は、cva の定義から variantsize の型を自動生成します。

import { cva, type VariantProps } from "class-variance-authority"

const buttonVariants = cva("...", {
variants: {
variant: { default: "...", destructive: "...", outline: "..." },
size: { default: "...", sm: "...", lg: "..." },
},
})

type ButtonVariants = VariantProps<typeof buttonVariants>
// => {
// variant?: "default" | "destructive" | "outline" | null | undefined
// size?: "default" | "sm" | "lg" | null | undefined
// }

定義を変えれば、型も自動で追従します。

// cva に ghost / link を足すと…
variant: { default: "...", destructive: "...", outline: "...", ghost: "...", link: "..." }

// VariantProps も自動で更新される
// variant?: "default" | "destructive" | "outline" | "ghost" | "link" | null

「定義と型がズレない」のが、cva + VariantProps の大きな利点です。

③ 追加の prop

最後に、Button 独自の asChild を足します。

{ asChild?: boolean }

この 3 つを & でつなぐと、Button の完全な型ができあがります。

React.ComponentProps<"button"> (button の標準 props + ref)
&
VariantProps<typeof buttonVariants> (cva 由来の variant / size)
&
{ asChild?: boolean } (独自 prop)
=
Button の引数の型

React 19 では forwardRef のジェネリクスが要らない

以前は、ref の型を forwardRef のジェネリクスで指定していました。

// React 18 以前
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => { ... })
// ↑ ref の型 ↑ props の型

React 19 では ref が通常の props として渡されるようになり、その型は React.ComponentProps<"button"> に含まれているので、関数の引数に型を書くだけで済みます。forwardRef のジェネリクスは不要になりました。

// React 19
function Button({ ...props }: React.ComponentProps<"button"> & ...) { ... }

型安全な使用例

自動補完

<Button variant="" />
// "" の中で補完を出すと:
// "default" / "destructive" / "outline" / "secondary" / "ghost" / "link"

タイポ・存在しない値の検出

<Button variant="primry" /> // ❌ "primary" のつもりが誤字 → 型エラー
<Button size="extra-large" /> // ❌ 存在しないサイズ → 型エラー

ref の型チェック

const buttonRef = useRef<HTMLButtonElement>(null)
<Button ref={buttonRef}>OK</Button> // ✅

const inputRef = useRef<HTMLInputElement>(null)
<Button ref={inputRef}>NG</Button> // ❌ input 用の ref は渡せない

少し高度な型

必要に応じて、標準の型ユーティリティも使えます。

// className を外部から渡せないようにする(Omit で除外)
type StrictButtonProps = Omit<React.ComponentProps<"button">, "className"> &
VariantProps<typeof buttonVariants>

// 一部の props を任意にする(Partial)
type PartialButtonProps = Partial<ButtonProps>

初めは使う機会は少ないですが、「こういう調整もできる」と知っておくと役立ちます。

型定義のベストプラクティス

  1. HTML 属性は ComponentProps で取り込む(手書きしない)
  2. バリアントの型は VariantProps で自動生成する(定義とズレさせない)
  3. 明確な型名を使うProps ではなく ButtonProps
  4. 利用者が使う型は export するexport type ButtonProps / export { Button, buttonVariants }

この章のまとめ

  • Button の型は ComponentProps<"button"> + VariantProps + { asChild } の交差型
  • React.ComponentProps<"button"> は button の全 props(ref 含む)をまとめて取り込む
  • VariantProps は cva の定義から型を自動生成し、定義とズレない
  • React 19 では forwardRef のジェネリクスが不要になり、引数に型を書くだけでよい

次章では、ここまで学んだすべてを使って、Button をゼロから組み立てます。