ハイブリッドモード、段階的コンポーネント書き換え、依存性注入更新を使用してAngularJSからAngularへ移行します。AngularJSアプリケーションのアップグレード、フレームワーク移行の計画、またはレガシーAngularコードのモダナイゼーション時に使用します。
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スクリプト
ベストプラクティス
- 段階的移行: すべてを一度に移行しない
- 徹底的にテスト: 各ステップで包括的テスト
- Codemodを使用: 繰り返し変換を自動化
- シンプルから開始: リーフコンポーネントから始める
- StrictModeを活用: 早期に問題を検出
- パフォーマンスを監視: 変更前後を測定
- 変更を文書化: 移行ログを保持
一般的な落とし穴
- useEffect依存関係を忘れる
- useMemo/useCallbackを過度に使用
- useEffectでクリーンアップを処理しない
- クラスと関数パターンを混在させる
- StrictMode警告を無視
- 破壊的変更の仮定
レビュー
まだレビューはありません。使ってみた感想をお寄せください。
同じリポジトリのスキル
概要と使いどころ
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.
日本語の概要は準備中です。原文の説明を表示しています。
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.
日本語の概要は準備中です。原文の説明を表示しています。
開発者を喜ばせる直感的でスケーラブルで保守可能なAPIを構築するために、RESTとGraphQL APIの設計原則をマスターします。新しいAPIの設計、API仕様のレビュー、またはAPI設計標準の確立時に使用してください。
クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ、ドメイン駆動設計を含む実証済みのバックエンドアーキテクチャパターンを実装します。複雑なバックエンドシステムの設計や、保守性向上のための既存アプリケーションのリファクタリング時に使用します。
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.
日本語の概要は準備中です。原文の説明を表示しています。