0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercel公式「React Best Practices」Skillsでコードはどう変わるのか試してみた

0
Posted at

はじめに

AIを使ったフロントエンド開発が当たり前になる中で、「とりあえず動くコード」は誰でも一瞬で生成できるようになりました。しかし、無駄な再レンダリングの防止やアクセシビリティの担保など、実運用に耐えうる「品質の高いコード」をAIに一発で書かせるのは、意外と難しいと感じています。

そんな中、Vercelから「React Best Practices」が発表されました。AIエージェントやLLM向けに最適化された構造化リポジトリです。

私たちは、10年以上にわたるReactとNext.jsの最適化に関する知識をreact-best-practices、AIエージェントとLLM向けに最適化された構造化リポジトリに集約しました。
Introducing React Best Practices より)

この公式Skillを活用することで、AIが「Vercelのトップエンジニアの知見」を持った状態でコードを書いてくれるようになります。

これを使えば、Reactに不慣れなチームでも、開発がラクになるのでは?と思ったので汎用的なButtonコンポーネントを作って比較してみました。

実践

今回は、フロントエンド開発でも身近である「汎用的なButtonコンポーネント」を例に通常のAI生成コードと、Vercelのスキルを適用したAI生成コードを比較してみました。

※今回の検証環境

  • ツール: GitHub Copilot CLI
  • モデル: Claude 4.6 Sonnet

インストール

npx skills add vercel-labs/agent-skills

スキル未使用:通常のAIが生成したButton

まずは、特に何もコンテキストを与えずに「汎用的なButtonコンポーネントを作って」と指示した場合のコード(Button1)です。

import type { ButtonHTMLAttributes } from "react";

type Variant = "primary" | "secondary" | "danger";
type Size = "sm" | "md" | "lg";

interface Button1Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: Variant;
  size?: Size;
}

const variantStyles: Record<Variant, string> = {
  primary:
    "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 dark:bg-blue-500 dark:hover:bg-blue-600",
  secondary:
    "bg-gray-100 text-gray-800 hover:bg-gray-200 active:bg-gray-300 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600",
  danger:
    "bg-red-600 text-white hover:bg-red-700 active:bg-red-800 dark:bg-red-500 dark:hover:bg-red-600",
};

const sizeStyles: Record<Size, string> = {
  sm: "px-3 py-1.5 text-sm",
  md: "px-4 py-2 text-base",
  lg: "px-6 py-3 text-lg",
};

function Button({
  variant = "primary",
  size = "md",
  className = "",
  disabled,
  children,
  ...props
}: Button1Props) {
  return (
    <button
      {...props}
      disabled={disabled}
      className={[
        "inline-flex items-center justify-center rounded-xl font-medium transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
        variantStyles[variant],
        sizeStyles[size],
        className,
      ].join(" ")}
    >
      {children}
    </button>
  );
}

export default Button;

image.png

Vercelスキル使用:最適化されたButton

次に、VercelのReact Best Practicesスキルを読み込ませた状態で同じ指示を出してみたコード(Button2)です。

import { memo } from "react";

export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost";
export type ButtonSize = "sm" | "md" | "lg";

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  children: React.ReactNode;
}

// Hoisted outside component to avoid recreation on each render (rendering-hoist-jsx / rerender-memo-with-default-value)
const BASE =
  "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50";

const VARIANT_STYLES: Record<ButtonVariant, string> = {
  primary:
    "bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-600 dark:bg-blue-500 dark:hover:bg-blue-400",
  secondary:
    "bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-400 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700",
  outline:
    "border border-gray-300 bg-transparent text-gray-700 hover:bg-gray-50 focus-visible:ring-gray-400 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-800",
  ghost:
    "bg-transparent text-gray-700 hover:bg-gray-100 focus-visible:ring-gray-400 dark:text-gray-200 dark:hover:bg-gray-800",
};

const SIZE_STYLES: Record<ButtonSize, string> = {
  sm: "h-8 px-3 text-sm",
  md: "h-10 px-4 text-sm",
  lg: "h-12 px-6 text-base",
};

// React.memo prevents unnecessary re-renders when parent re-renders (rerender-memo)
const Button = memo(function Button({
  variant = "primary",
  size = "md",
  className = "",
  children,
  ...props
}: ButtonProps) {
  const classes = `${BASE} ${VARIANT_STYLES[variant]} ${SIZE_STYLES[size]} ${className}`;

  return (
    <button className={classes} {...props}>
      {children}
    </button>
  );
});

export default Button;

image.png

スキルを使って2つのコンポーネントをレビュー・比較させてみた

実際にこれら2つのコードを、Vercelスキルを持ったAI自身にレビュー・比較してもらいました。

image.png

レビュー結果からも分かる通り、単なるスタイリングの違いだけでなく、Reactの設計思想に基づいた明確な差が現れました。

  • 素晴らしい点(型の再利用性とアクセシビリティ)
    Button2 はPropsだけでなく ButtonVariant 等の型もexportしており、親コンポーネントからの拡張性が考慮されています。また、focus:outline-none によるマウス操作時のフォーカスリング課題を、focus-visible を使って適切に解決しています。

  • 賛否両論な点(過剰な最適化)
    Button2 は React.memo を使用しています。Vercelスキルはこれを「最適化」として高く評価しますが、汎用的な小さなコンポーネントに対しては「やりすぎ(オーバーエンジニアリング)」の場合があります。親から渡す関数(onClick など)に useCallback を徹底しないと意味をなさず、ただ比較の計算コストだけが増えてしまう可能性があります。また、手動でガチガチに最適化を書くスタイルは、自動最適化を目指すReact 19(React Compiler)の思想とはやや逆行しているようにも感じます

まとめ

「汎用的なButtonコンポーネントを作って」という全く同じシンプルなプロンプトでも、VercelのベストプラクティスをAIにインプットさせておくだけで、生成されるコードの品質に差がありました。

一方で今回の検証を通して、「ゼロからコードを生成させる」だけでなく、「レビュー時のサポート役」として活用するのも面白そうだと感じました。

AIに最初からすべて書かせると、プロジェクトの規模によっては少しオーバースペック(過剰な最適化)に感じるコードが出力されることもあります。そのため、まずは自分たちで書いたシンプルなコードに対して、「アクセシビリティ面で足りない部分はあるか?」「最適化できるポイントはあるか?」をAIにレビューしてもらう、といった使い方も相性が良さそうです。

AIが提示してくれるVercelの高度な知見を参考にしつつ、実際のプロジェクトのメンテナンス性に合わせて、必要な項目を柔軟に取り入れていく。そんな活用法も良いかもしれません。

とても簡単に導入できるので、気になった方はぜひ自身のプロジェクトで試してみてください。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?