React

コンポーネント

React

関数コンポーネント・props・条件レンダリング

関数コンポーネント

props・デフォルト値・children・型定義

Button.tsx tsx
import type { ReactNode, MouseEvent } from 'react';

// Props の型定義
interface ButtonProps {
  children:  ReactNode;
  onClick?:  (e: MouseEvent<HTMLButtonElement>) => void;
  variant?:  'primary' | 'secondary' | 'danger';
  size?:     'sm' | 'md' | 'lg';
  disabled?: boolean;
  className?: string;
}

// 関数コンポーネント
export function Button({
  children,
  onClick,
  variant = 'primary',   // デフォルト値
  size    = 'md',
  disabled = false,
  className = '',
}: ButtonProps) {
  const base = 'rounded font-medium transition-colors';
  const variants = {
    primary:   'bg-emerald-600 text-white hover:bg-emerald-700',
    secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
    danger:    'bg-red-600 text-white hover:bg-red-700',
  };
  const sizes = { sm: 'px-3 py-1 text-sm', md: 'px-4 py-2', lg: 'px-6 py-3 text-lg' };

  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`${base} ${variants[variant]} ${sizes[size]} ${className}`}
    >
      {children}
    </button>
  );
}

条件レンダリングとリスト

&&、三項演算子、map、key

rendering.tsx tsx
// 条件レンダリング
function UserPanel({ user, isLoading, error }) {
  // アーリーリターン(推奨)
  if (isLoading) return <Spinner />;
  if (error)     return <ErrorMessage message={error.message} />;
  if (!user)     return null;

  return (
    <div>
      {/* && 演算子: 左辺が truthy の時だけ右辺を表示 */}
      {user.isAdmin && <AdminBadge />}

      {/* 三項演算子 */}
      <span>{user.isActive ? '有効' : '無効'}</span>

      {/* 複数条件は変数に切り出す */}
      {user.role === 'admin' ? <AdminView /> : <UserView />}
    </div>
  );
}

// リストレンダリング
function UserList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map(user => (
        // key は一意で安定した値を使う(インデックスは最後の手段)
        <li key={user.id}>
          <UserCard user={user} />
        </li>
      ))}
      {users.length === 0 && <li>ユーザーが見つかりません</li>}
    </ul>
  );
}

コンポジション

children・slots・as prop パターン

composition.tsx tsx
// children による合成
function Card({ children, className = '' }) {
  return <div className={`rounded-xl border p-6 ${className}`}>{children}</div>;
}
function Card.Header({ children }) { return <div className="mb-4 font-bold">{children}</div>; }
function Card.Body({ children })   { return <div>{children}</div>; }

// 使用
<Card>
  <Card.Header>タイトル</Card.Header>
  <Card.Body>コンテンツ</Card.Body>
</Card>

// 複数スロット(named slots)
function Layout({ header, sidebar, children }) {
  return (
    <div className="grid grid-cols-[250px_1fr]">
      <aside>{sidebar}</aside>
      <div>
        <header>{header}</header>
        <main>{children}</main>
      </div>
    </div>
  );
}

// as prop(ポリモーフィックコンポーネント)
function Text({ as: Component = 'p', children, ...props }) {
  return <Component {...props}>{children}</Component>;
}
// <Text as="h1">見出し</Text>
// <Text as="span">インライン</Text>