본문으로 건너뛰기
cccskills
무료GitHub에 공개

react-modernization

Reactアプリケーションを最新バージョンにアップグレードし、クラスコンポーネントからフックへ移行し、並行機能を採用します。Reactコードベースのモダナイゼーション、React Hooksへの移行、または最新Reactバージョンへのアップグレード時に使用します。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md12.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

English | 日本語

Reactモダナイゼーション

Reactバージョンアップグレード、クラスからフックへの移行、並行機能の採用、自動変換のためのcodemodをマスターします。

このスキルを使用するタイミング

  • Reactアプリケーションを最新バージョンにアップグレード
  • クラスコンポーネントをフックを使用した関数コンポーネントに移行
  • 並行React機能(Suspense、トランジション)を採用
  • 自動リファクタリングのためにcodemodを適用
  • 状態管理パターンをモダナイズ
  • TypeScriptに更新
  • React 18+機能でパフォーマンスを改善

バージョンアップグレードパス

React 16 → 17 → 18

バージョン別の破壊的変更:

React 17:

  • イベント委譲の変更
  • イベントプーリングなし
  • エフェクトクリーンアップのタイミング
  • JSX変換(Reactインポート不要)

React 18:

  • 自動バッチング
  • 並行レンダリング
  • Strict Modeの変更(二重呼び出し)
  • 新しいルートAPI
  • サーバー上のSuspense

クラスからフックへの移行

状態管理

// Before: クラスコンポーネント
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: ''
    };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

// After: フックを使用した関数コンポーネント
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

ライフサイクルメソッドからフックへ

// Before: ライフサイクルメソッド
class DataFetcher extends React.Component {
  state = { data: null, loading: true };

  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  componentWillUnmount() {
    this.cancelRequest();
  }

  fetchData = async () => {
    const data = await fetch(`/api/${this.props.id}`);
    this.setState({ data, loading: false });
  };

  cancelRequest = () => {
    // クリーンアップ
  };

  render() {
    if (this.state.loading) return <div>Loading...</div>;
    return <div>{this.state.data}</div>;
  }
}

// After: useEffectフック
function DataFetcher({ id }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;

    const fetchData = async () => {
      try {
        const response = await fetch(`/api/${id}`);
        const result = await response.json();

        if (!cancelled) {
          setData(result);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          console.error(error);
        }
      }
    };

    fetchData();

    // クリーンアップ関数
    return () => {
      cancelled = true;
    };
  }, [id]); // idが変更されたときに再実行

  if (loading) return <div>Loading...</div>;
  return <div>{data}</div>;
}

ContextとHOCからフックへ

// Before: Contextコンシューマーとホック
const ThemeContext = React.createContext();

class ThemedButton extends React.Component {
  static contextType = ThemeContext;

  render() {
    return (
      <button style={{ background: this.context.theme }}>
        {this.props.children}
      </button>
    );
  }
}

// After: useContextフック
function ThemedButton({ children }) {
  const { theme } = useContext(ThemeContext);

  return (
    <button style={{ background: theme }}>
      {children}
    </button>
  );
}

// Before: データフェッチのためのHOC
function withUser(Component) {
  return class extends React.Component {
    state = { user: null };

    componentDidMount() {
      fetchUser().then(user => this.setState({ user }));
    }

    render() {
      return <Component {...this.props} user={this.state.user} />;
    }
  };
}

// After: カスタムフック
function useUser() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then(setUser);
  }, []);

  return user;
}

function UserProfile() {
  const user = useUser();
  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

React 18並行機能

新しいルートAPI

// Before: React 17
import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));

// After: React 18
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

自動バッチング

// React 18: すべての更新がバッチ処理される
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // 1回だけ再レンダリング(バッチ処理)
}

// 非同期でも:
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 18でもバッチ処理される!
}, 1000);

// 必要に応じてオプトアウト
import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1);
});
// ここで再レンダリングが発生
setFlag(f => !f);
// 別の再レンダリング

トランジション

import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    // 緊急: 入力を即座に更新
    setQuery(e.target.value);

    // 緊急でない: 結果を更新(中断可能)
    startTransition(() => {
      setResults(searchResults(e.target.value));
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <Results data={results} />
    </>
  );
}

データフェッチのためのSuspense

import { Suspense } from 'react';

// リソースベースのデータフェッチ(React 18で)
const resource = fetchProfileData();

function ProfilePage() {
  return (
    <Suspense fallback={<Loading />}>
      <ProfileDetails />
      <Suspense fallback={<Loading />}>
        <ProfileTimeline />
      </Suspense>
    </Suspense>
  );
}

function ProfileDetails() {
  // データの準備ができていない場合、サスペンドされる
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline() {
  const posts = resource.posts.read();
  return <Timeline posts={posts} />;
}

自動化のためのCodemod

React Codemodを実行

# jscodeshiftをインストール
npm install -g jscodeshift

# React 16.9 codemod(unsafe lifecycleメソッドをリネーム)
npx react-codeshift <transform> <path>

# 例: UNSAFE_メソッドをリネーム
npx react-codeshift --parser=tsx \
  --transform=react-codeshift/transforms/rename-unsafe-lifecycles.js \
  src/

# 新しいJSX変換に更新(React 17+)
npx react-codeshift --parser=tsx \
  --transform=react-codeshift/transforms/new-jsx-transform.js \
  src/

# クラスからフックへ(サードパーティ)
npx codemod react/hooks/convert-class-to-function src/

カスタムCodemodの例

// custom-codemod.js
module.exports = function(file, api) {
  const j = api.jscodeshift;
  const root = j(file.source);

  // setState呼び出しを検索
  root.find(j.CallExpression, {
    callee: {
      type: 'MemberExpression',
      property: { name: 'setState' }
    }
  }).forEach(path => {
    // useStateに変換
    // ... 変換ロジック
  });

  return root.toSource();
};

// 実行: jscodeshift -t custom-codemod.js src/

パフォーマンス最適化

useMemoとuseCallback

function ExpensiveComponent({ items, filter }) {
  // 高価な計算をメモ化
  const filteredItems = useMemo(() => {
    return items.filter(item => item.category === filter);
  }, [items, filter]);

  // 子の再レンダリングを防ぐためにコールバックをメモ化
  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []); // 依存関係なし、変更されない

  return (
    <List items={filteredItems} onClick={handleClick} />
  );
}

// memoを使用した子コンポーネント
const List = React.memo(({ items, onClick }) => {
  return items.map(item => (
    <Item key={item.id} item={item} onClick={onClick} />
  ));
});

コード分割

import { lazy, Suspense } from 'react';

// コンポーネントを遅延読み込み
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

TypeScript移行

// Before: JavaScript
function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

// After: TypeScript
interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
}

function Button({ onClick, children }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}

// ジェネリックコンポーネント
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <>{items.map(renderItem)}</>;
}

移行チェックリスト

### 移行前
- [ ] 依存関係を段階的に更新(すべて一度にではなく)
- [ ] リリースノートの破壊的変更をレビュー
- [ ] テストスイートをセットアップ
- [ ] フィーチャーブランチを作成

### クラス → フック移行
- [ ] 移行するクラスコンポーネントを特定
- [ ] リーフコンポーネント(子なし)から開始
- [ ] 状態をuseStateに変換
- [ ] ライフサイクルをuseEffectに変換
- [ ] contextをuseContextに変換
- [ ] カスタムフックを抽出
- [ ] 徹底的にテスト

### React 18アップグレード
- [ ] まずReact 17に更新(必要に応じて)
- [ ] reactとreact-domを18に更新
- [ ] TypeScript使用時は@types/reactを更新
- [ ] createRoot APIに変更
- [ ] StrictModeでテスト(二重呼び出し)
- [ ] 並行レンダリングの問題に対処
- [ ] 有益な場合はSuspense/Transitionsを採用

### パフォーマンス
- [ ] パフォーマンスボトルネックを特定
- [ ] 適切な箇所にReact.memoを追加
- [ ] 高価な操作にuseMemo/useCallbackを使用
- [ ] コード分割を実装
- [ ] 再レンダリングを最適化

### テスト
- [ ] テストユーティリティを更新(React Testing Library)
- [ ] React 18機能でテスト
- [ ] コンソールの警告を確認
- [ ] パフォーマンステスト

リソース

  • references/breaking-changes.md: バージョン固有の破壊的変更
  • references/codemods.md: Codemod使用ガイド
  • references/hooks-migration.md: 包括的フックパターン
  • references/concurrent-features.md: React 18並行機能
  • assets/codemod-config.json: Codemod設定
  • assets/migration-checklist.md: ステップバイステップチェックリスト
  • scripts/apply-codemods.sh: 自動化codemodスクリプト

ベストプラクティス

  1. 段階的移行: すべてを一度に移行しない
  2. 徹底的にテスト: 各ステップで包括的テスト
  3. Codemodを使用: 繰り返し変換を自動化
  4. シンプルから開始: リーフコンポーネントから始める
  5. StrictModeを活用: 早期に問題を検出
  6. パフォーマンスを監視: 変更前後を測定
  7. 変更を文書化: 移行ログを保持

一般的な落とし穴

  • useEffect依存関係を忘れる
  • useMemo/useCallbackを過度に使用
  • useEffectでクリーンアップを処理しない
  • クラスと関数パターンを混在させる
  • StrictMode警告を無視
  • 破壊的変更の仮定

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

ハイブリッドモード、段階的コンポーネント書き換え、依存性注入更新を使用してAngularJSからAngularへ移行します。AngularJSアプリケーションのアップグレード、フレームワーク移行の計画、またはレガシーAngularコードのモダナイゼーション時に使用します。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

開発者を喜ばせる直感的でスケーラブルで保守可能なAPIを構築するために、RESTとGraphQL APIの設計原則をマスターします。新しいAPIの設計、API仕様のレビュー、またはAPI設計標準の確立時に使用してください。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers. Use when designing new APIs, reviewing API specifications, or establishing API design standards.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

Implement proven backend architecture patterns including Clean Architecture, Hexagonal Architecture, and Domain-Driven Design. Use when architecting complex backend systems or refactoring existing applications for better maintainability.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ、ドメイン駆動設計を含む実証済みのバックエンドアーキテクチャパターンを実装します。複雑なバックエンドシステムの設計や、保守性向上のための既存アプリケーションのリファクタリング時に使用します。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

amurata/cc-tools62025년 11월 28일 업데이트

amurata의 스킬 모두 보기

이 스킬의 문제 신고