😇

React Best Practicesはむしろアンチパターン説

に公開
1

概要

先日、Vercel社からAIコーディング用のReact Best Practicesが公開された。

https://vercel.com/blog/introducing-react-best-practices

いわく、「 Vercelが10年以上にわたって蓄積してきたReactとNext.jsの最適化ノウハウを体系化したもの 」となっており、40以上のルールが8つのカテゴリーに分類された力の入ったルール集となっている。

しかしSNSでは評判が良くない。

Skillsを使って書いたコードをレビューする時に手直しすることの方が多くて、この記事を書いたというサンクコストもあって信じたくはないがそんなに有用とは今のところ思えない。

ref: https://qiita.com/RioooJackpot/items/fee34d0e7dc119ed01a0

そこでClaude Sonnetくんにルールのコードレビューをしてもらった。

評価プロンプト

Nextjs上級者ならそもそもベストプラクティスのお膳立てがなくても困らないわけで、ターゲットは俺みたいな弱者のはず。ということで初心者〜中級者向けにした。

react-best-practices/rulesにある各nextjsのベストプラクティスルールについて
react nextjs初級〜中級者向けに適しているのが何個か調べたい

./result.mdに結果を箇条書きでマルバツで書いて最後に集計結果書いて

結果発表〜〜〜

  • 初級〜中級者向けに適している: 30個
  • 上級者向け: 16個

65.2%はOKと判定しつつ、16個はNGだった。

具体的に見ていくと

複雑、高度すぎると指摘されたパターン

// react-best-practices/rules/advanced-event-handler-refs.md
// **Incorrect (re-subscribes on every render):**
function useWindowEvent(event: string, handler: () => void) {
  useEffect(() => {
    window.addEventListener(event, handler)
    return () => window.removeEventListener(event, handler)
  }, [event, handler])
}

// **Correct (stable subscription):**
function useWindowEvent(event: string, handler: () => void) {
  const handlerRef = useRef(handler)
  useEffect(() => {
    handlerRef.current = handler
  }, [handler])

  useEffect(() => {
    const listener = () => handlerRef.current()
    window.addEventListener(event, listener)
    return () => window.removeEventListener(event, listener)
  }, [event])
}

不必要に外部ライブラリに頼っているパターンがあった

// react-best-practices/rules/async-dependencies.md
// **Incorrect (profile waits for config unnecessarily):**
const [user, config] = await Promise.all([
  fetchUser(),
  fetchConfig()
])
const profile = await fetchProfile(user.id)

// **Correct (config and profile run in parallel):**
import { all } from 'better-all'

const { user, config, profile } = await all({
  async user() { return fetchUser() },
  async config() { return fetchConfig() },
  async profile() {
    return fetchProfile((await this.$.user).id)
  }
})

まとめ

このルール集の良し悪しがつくなら別に自分でまとめればいいわけで大半の人は「とりあえず喰わせておけ」ってなると思う。評価プロンプトが「メンテナンス性の視点にたって」とか「常識的なのは除いて」とか入れたらまた変わるかもしれない。

以下詳細な評価レビュー

React/Next.js ベストプラクティスルール - 初級〜中級者向け適性評価

評価基準

  • ○: 初級〜中級者向けに適している
  • ×: 上級者向け、または初級〜中級者には難しい

評価結果

Advanced(上級者向け)

  1. advanced-event-handler-refs.md - ×

    • Refを使ったイベントハンドラーの最適化
    • 理由: useRefとuseEffectの高度な組み合わせ、クロージャの理解が必要
  2. advanced-use-latest.md - ×

    • useLatestパターンの実装
    • 理由: カスタムフックの作成、依存配列の高度な理解が必要

Async(非同期処理)

  1. async-api-routes.md - ○

    • APIルートでのウォーターフォール防止
    • 理由: Promise.allの基本的な使い方、並列処理の概念は中級者で理解可能
  2. async-defer-await.md - ○

    • awaitの遅延実行
    • 理由: 条件分岐とasync/awaitの基本的な組み合わせ
  3. async-dependencies.md - ×

    • better-allを使った依存関係ベースの並列化
    • 理由: 外部ライブラリの使用、複雑な依存関係の管理
  4. async-parallel.md - ○

    • Promise.allによる並列実行
    • 理由: Promise.allは中級者の基本スキル
  5. async-suspense-boundaries.md - ×

    • Suspenseの戦略的配置
    • 理由: Suspenseの高度な使い方、ストリーミングの理解が必要

Bundle(バンドル最適化)

  1. bundle-barrel-imports.md - ○

    • バレルファイルインポートの回避
    • 理由: インポート文の書き方の違いだけで理解しやすい
  2. bundle-conditional.md - ○

    • 条件付きモジュールローディング
    • 理由: dynamic importの基本的な使い方
  3. bundle-defer-third-party.md - ○

    • サードパーティライブラリの遅延読み込み
    • 理由: next/dynamicの基本的な使い方
  4. bundle-dynamic-imports.md - ○

    • 重いコンポーネントの動的インポート
    • 理由: next/dynamicの基本的な使い方、コード分割の基本概念
  5. bundle-preload.md - ×

    • ユーザー意図に基づくプリロード
    • 理由: パフォーマンス最適化の高度なテクニック

Client(クライアント側)

  1. client-event-listeners.md - ×

    • グローバルイベントリスナーの重複排除
    • 理由: useSWRSubscriptionの使用、モジュールレベルのMap管理
  2. client-swr-dedup.md - ○

    • SWRによる自動重複排除
    • 理由: SWRの基本的な使い方は中級者で習得可能

JavaScript最適化

  1. js-batch-dom-css.md - ○

    • DOM CSSの一括変更
    • 理由: クラスの切り替えは基本的なテクニック
  2. js-cache-function-results.md - ○

    • 関数結果のキャッシュ
    • 理由: Mapを使ったキャッシュは理解しやすい
  3. js-cache-property-access.md - ○

    • ループ内のプロパティアクセスのキャッシュ
    • 理由: 基本的な最適化テクニック
  4. js-cache-storage.md - ○

    • Storage API呼び出しのキャッシュ
    • 理由: localStorageの基本的な使い方
  5. js-combine-iterations.md - ○

    • 複数の配列イテレーションの結合
    • 理由: 配列操作の基本的な最適化
  6. js-early-exit.md - ○

    • 関数の早期リターン
    • 理由: 基本的なコーディングパターン
  7. js-hoist-regexp.md - ○

    • RegExpの巻き上げ
    • 理由: 変数の巻き上げは基本概念
  8. js-index-maps.md - ○

    • 繰り返し検索用のインデックスマップ構築
    • 理由: Mapの基本的な使い方
  9. js-length-check-first.md - ○

    • 配列比較の長さチェック優先
    • 理由: 基本的な最適化テクニック
  10. js-min-max-loop.md - ○

    • ソートの代わりにループで最小/最大値を取得
    • 理由: 基本的なアルゴリズム
  11. js-set-map-lookups.md - ○

    • Set/Mapを使ったO(1)検索
    • 理由: データ構造の基本的な使い方
  12. js-tosorted-immutable.md - ○

    • イミュータビリティのためのtoSorted()使用
    • 理由: 新しいメソッドだが概念は理解しやすい

Rendering(レンダリング)

  1. rendering-activity.md - ×

    • 表示/非表示にActivityコンポーネントを使用
    • 理由: カスタムコンポーネントパターンの理解が必要
  2. rendering-animate-svg-wrapper.md - ○

    • SVG要素の代わりにラッパーをアニメーション
    • 理由: CSSアニメーションの基本的なテクニック
  3. rendering-conditional-render.md - ○

    • 明示的な条件付きレンダリング
    • 理由: JSXの基本的な書き方
  4. rendering-content-visibility.md - ×

    • 長いリストにCSS content-visibility使用
    • 理由: 新しいCSS機能、パフォーマンス最適化の高度な知識
  5. rendering-hoist-jsx.md - ○

    • 静的JSX要素の巻き上げ
    • 理由: 基本的な最適化パターン
  6. rendering-hydration-no-flicker.md - ×

    • ハイドレーションミスマッチのちらつき防止
    • 理由: SSRとハイドレーションの高度な理解が必要
  7. rendering-svg-precision.md - ○

    • SVG精度の最適化
    • 理由: SVGOツールの使い方は理解しやすい

Re-render(再レンダリング)

  1. rerender-defer-reads.md - ○

    • 状態の読み取りを使用箇所まで遅延
    • 理由: 基本的な最適化パターン
  2. rerender-dependencies.md - ○

    • useEffectの依存配列の絞り込み
    • 理由: useEffectの基本的な使い方
  3. rerender-derived-state.md - ×

    • 派生状態のサブスクライブ
    • 理由: useSyncExternalStoreの使用、高度なパターン
  4. rerender-functional-setstate.md - ○

    • 関数型setStateの使用
    • 理由: useStateの基本的な使い方
  5. rerender-lazy-state-init.md - ○

    • 遅延状態初期化の使用
    • 理由: useStateの基本的な最適化
  6. rerender-memo.md - ○

    • メモ化コンポーネントへの抽出
    • 理由: React.memoは中級者の基本スキル
  7. rerender-transitions.md - ×

    • 緊急でない更新にTransitionsを使用
    • 理由: useTransitionは比較的新しい機能、高度な使い方

Server(サーバー側)

  1. server-after-nonblocking.md - ×

    • ノンブロッキング操作にafter()を使用
    • 理由: Next.js 15の新機能、サーバー側の高度な最適化
  2. server-cache-lru.md - ×

    • クロスリクエストLRUキャッシング
    • 理由: LRUキャッシュの実装、サーバー側の高度な知識
  3. server-cache-react.md - ×

    • React.cache()によるリクエストごとの重複排除
    • 理由: React Server Componentsの高度な機能
  4. server-parallel-fetching.md - ○

    • コンポーネント構成による並列データフェッチ
    • 理由: RSCの基本的な使い方、並列処理の概念
  5. server-serialization.md - ×

    • RSC境界でのシリアライゼーション最小化
    • 理由: React Server Componentsの高度な理解が必要

集計結果

  • ○(初級〜中級者向けに適している): 30個
  • ×(上級者向け): 16個

適している割合

  • 65.2% のルールが初級〜中級者向けに適している

カテゴリ別の適性

初級〜中級者向けが多いカテゴリ

  • JavaScript最適化: 12/12 (100%)
  • Bundle最適化: 4/5 (80%)
  • Async処理: 3/5 (60%)

上級者向けが多いカテゴリ

  • Advanced: 0/2 (0%)
  • Server: 1/5 (20%)
  • Client: 1/2 (50%)

推奨学習順序(初級〜中級者向け)

  1. まず学ぶべき(基本)

    • JavaScript最適化系(js-*)
    • 基本的なレンダリング最適化
    • Promise.allなどの基本的な非同期処理
  2. 次に学ぶべき(中級)

    • Bundle最適化(dynamic import、code splitting)
    • React.memoなどのメモ化
    • SWRなどのデータフェッチライブラリ
  3. その後学ぶべき(上級への橋渡し)

    • Suspenseの使い方
    • Server Componentsの基本
    • 高度なフック(useTransitionなど)

Discussion

Honey32Honey32

失礼します。「そのルールが、初級〜中級者向けに適している / いない」とは、以下のどちらの意味合いでしょうか?

  • 初級者~中級者にとって、《自分が書く際、参考にするのに》適している / いない
  • 初級者~中級者にとって、《AI に読み込ませて利用するのに》適している / いない

なぜこの部分が気になるのかというと、「自分で書くことができなくても、AI が書き出したものを見るぐらいなら理解できなくもない」という、両者の間の領域であれば、AI に読み込ませて利用することに差し支えないと思うからです。


(追記)特に、 server-serialization なんかは、「初心者が RSC を使うとつまづきやすい部分」です。むしろ理解しないまま自力で書くよりも、 AI に読み込ませておいて使ったほうがマシになるような性質を持っていると思います。なので、前者の解釈で「まあ理解するのは後でええか」とするのは正しいですが、後者の解釈で「高度だから初心者に向かない」というのは、むしろ矛盾しています。