mcpField
R
react-modernization

react-modernization

github.com/wshobson開発41.9k12.8k 回インストール2 週間前に更新
リポジトリ

react-modernization について

バージョン更新、クラスコンポーネントからフックへの移行、React 18 機能の導入を通じて、React アプリを近代化するスキルです。React プロジェクトを保守・改修する開発者向けです。

主な機能

  1. 01React のバージョン更新ガイド
  2. 02クラスコンポーネントからフックへの移行
  3. 03React 18 の並行機能
  4. 04Codemod によるリファクタリング
  5. 05状態管理パターンの近代化

活用シーン

  1. 01既存の React アプリのアップグレード
  2. 02クラスコンポーネントをフックに置換
  3. 03Suspense と transitions の導入
  4. 04TypeScript に合わせたプロジェクトパターンの更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshobson/agents --skill react-modernization

インストール後、Claude Code で/react-modernizationを入力して読み込まれたか確認します。

同梱ファイルreferences/details.md

詳細

React モダナイゼーション

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

このスキルを使う場面

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

バージョンアップグレードの道筋

React 16 → 17 → 18

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

React 17:

  • イベント委譲の変更
  • イベントプーリングの廃止
  • エフェクトのクリーンアップのタイミング
  • JSX トランスフォーム(React のインポートが不要に)

React 18:

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

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

状態管理
// Before: Class component
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: Functional component with hooks
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: Lifecycle methods
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 = () => {
    // Cleanup
  };

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

// After: useEffect hook
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();

    // Cleanup function
    return () => {
      cancelled = true;
    };
  }, [id]); // Re-run when id changes

  if (loading) return <div>Loading...</div>;
  return <div>{data}</div>;
}
Context と HOC からフックへ
// Before: Context consumer and HOC
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 hook
function ThemedButton({ children }) {
  const { theme } = useContext(ThemeContext);

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

// Before: HOC for data fetching
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: Custom hook
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 の並行機能

新しい root 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: All updates are batched
function handleClick() {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // Only one re-render (batched)
}

// Even in async:
setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // Still batched in React 18!
}, 1000);

// Opt out if needed
import { flushSync } from "react-dom";

flushSync(() => {
  setCount((c) => c + 1);
});
// Re-render happens here
setFlag((f) => !f);
// Another re-render
トランジション
import { useState, useTransition } from "react";

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

  const handleChange = (e) => {
    // Urgent: Update input immediately
    setQuery(e.target.value);

    // Non-urgent: Update results (can be interrupted)
    startTransition(() => {
      setResults(searchResults(e.target.value));
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <Results data={results} />
    </>
  );
}
データ取得のための Suspense
import { Suspense } from "react";

// Resource-based data fetching (with React 18)
const resource = fetchProfileData();

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

function ProfileDetails() {
  // This will suspend if data not ready
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

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

その他のパターンとテンプレート

より詳細なテンプレートと実例は references/details.md にあります。パターンライブラリの全体については、そのファイルをお読みください。

コメント