react-native-ease-refactor
React NativeプロジェクトのコードベースをスキャンしてAnimated・Reanimatedの実装を検出し、EaseViewへの移行を自動で行います。既存のアニメーションコードをモダンな構成に置き換えたいときに活躍するスキルです。
description の原文を見る
Scan for Animated/Reanimated code and migrate to EaseView
SKILL.md 本文
react-native-ease refactor
あなたは react-native-reanimated と React Native の組み込み Animated API コードを react-native-ease の EaseView コンポーネントに変換する移行アシスタントです。
以下の 6 つのフェーズを正確に従ってください。フェーズをスキップしたり、順序を変えたりしてはいけません。
Phase 1: Discovery(発見)
ユーザーのプロジェクトをスキャンしてアニメーションコードを検出します:
-
Grep を使用してプロジェクトが NativeWind を使用しているかを検出します:
- パターン:
**/*.{ts,tsx,js,jsx}内のfrom ['"]nativewind['"] - また
package.jsonの依存関係で"nativewind"をチェックします - NativeWind が検出された場合、フェーズ 5 で使用するために
usesNativeWind = trueフラグを設定します
- パターン:
-
Reanimated のバージョンを検出します(フェーズ 2 でのデフォルト値マッピングに必要):
package.jsonを読み込み、dependenciesまたはdevDependenciesのreact-native-reanimatedのバージョンをチェックします- バージョンが
^4または>=4.0.0の場合、reanimatedVersion = 4を設定します - それ以外の場合は
reanimatedVersion = 3を設定します(v2/v3 は同じデフォルト値を共有)
-
Grep を使用して
react-native-reanimatedからインポートしているすべてのファイルを検出します:- パターン:
from ['"]react-native-reanimated['"] **/*.{ts,tsx,js,jsx}で検索
- パターン:
-
React Native の組み込み
AnimatedAPI を使用しているすべてのファイルを Grep で検出します:- パターン:
from ['"]react-native['"]でAnimatedも使用している - パターン:
Animated\.View|Animated\.Text|Animated\.Image|Animated\.Value|Animated\.timing|Animated\.spring
- パターン:
-
react-native-easeを既に使用しているファイルを Grep で検出します(再移行を避けるため):- パターン:
from ['"]react-native-ease['"]
- パターン:
-
アニメーションコードを含む各ファイルを読みます。アニメーションパターンを持つコンポーネントのリストを作成します。
スキャンから除外する:
node_modules/*.test.*および*.spec.*ファイル- ビルド出力ディレクトリ(
lib/、build/、dist/)
Phase 2: Classification(分類)
見つかった各コンポーネントを 移行可能 または 移行不可 として分類します。
Decision Tree(決定木)
これらのチェックを順に適用します。最初にマッチしたものが結果を決定します:
- ジェスチャー API を使用している? (
Gesture.Pan,Gesture.Pinch,Gesture.Rotation,useAnimatedGestureHandler) → 移行不可 — "ジェスチャー駆動アニメーション" - スクロールハンドラを使用している? (
useAnimatedScrollHandler,onScrollwithAnimated.event) → 移行不可 — "スクロール駆動アニメーション" - 共有要素トランジションを使用している? (
sharedTransitionTag) → 移行不可 — "共有要素トランジション" runOnUIまたはワークレット指令を使用している? → 移行不可 — "ワークレットランタイムが必要"withSequenceを使用している? → 移行不可 — "アニメーションシーケンシングはサポートされていません" 5b.withDelayが単一のアニメーション(withTiming/withSpring)をラップしている? → 移行可能 — トランジションのdelayにマップします 5c.withDelayがwithSequenceまたは入れ子になったwithDelayをラップしている? → 移行不可 — "複雑な遅延/シーケンシングはサポートされていません"- 複雑な
interpolate()を使用している? (2 つ以上の入力/出力値) → 移行不可 — "複雑な補間" layout={...}プロパティを使用している? → 移行不可 — "レイアウトアニメーション"- サポートされていないプロパティをアニメート化している? (サポート対象外: opacity、translateX、translateY、scale、scaleX、scaleY、rotate、rotateX、rotateY、borderRadius、backgroundColor、borderWidth、borderColor、shadowOpacity、shadowRadius、shadowColor、shadowOffset、elevation) → 移行不可 — "サポートされていないプロパティをアニメート化:
<prop>" - プロパティごとに異なるトランジション設定を使用している? (例: opacity は 200ms タイミング、scale はスプリング) → 移行可能 —
TransitionMapにカテゴリキー(transform、opacity、borderRadius、backgroundColor、border、shadow、default)でマップします - 状態駆動ではない? (アニメーションがジェスチャー/スクロール値によってトリガーされ、React 状態ではない) → 移行不可 — "状態駆動ではない"
- その他 → 移行可能
Migratable Pattern Mapping(移行可能パターンマッピング)
Reanimated/Animated パターンを EaseView に変換するには、この表を使用します:
| Reanimated / Animated パターン | EaseView 同等物 |
|---|---|
useSharedValue + useAnimatedStyle + withTiming (opacity、translate、scale、rotate、borderRadius、backgroundColor) | animate={{ prop: value }} + transition={{ type: 'timing', duration, easing }} |
withSpring | transition={{ type: 'spring', damping, stiffness, mass }} |
entering={FadeIn} / FadeIn.duration(N) | initialAnimate={{ opacity: 0 }} + animate={{ opacity: 1 }} + タイミングトランジション |
entering={FadeInDown} / FadeInUp | initialAnimate={{ opacity: 0, translateY: ±value }} + animate={{ opacity: 1, translateY: 0 }} |
entering={SlideInLeft} / SlideInRight | initialAnimate={{ translateX: ±value }} + animate={{ translateX: 0 }} |
entering={SlideInUp} / SlideInDown | initialAnimate={{ translateY: ±value }} + animate={{ translateY: 0 }} |
entering={ZoomIn} | initialAnimate={{ scale: 0 }} + animate={{ scale: 1 }} |
exiting={FadeOut} / その他の終了アニメーション | 状態駆動の終了: ブール状態 + onTransitionEnd でアンマウント(レポートで "状態変更が必要" にフラグを立てます) |
withRepeat(withTiming(...), -1, false) | transition={{ type: 'timing', ..., loop: 'repeat' }} + 開始値用 initialAnimate |
withRepeat(withTiming(...), -1, true) | transition={{ type: 'timing', ..., loop: 'reverse' }} + 開始値用 initialAnimate |
Easing.linear | easing: 'linear' |
Easing.ease / Easing.inOut(Easing.ease) | easing: 'easeInOut' |
Easing.in(Easing.ease) | easing: 'easeIn' |
Easing.out(Easing.ease) | easing: 'easeOut' |
Easing.bezier(x1, y1, x2, y2) | easing: [x1, y1, x2, y2] |
Animated.Value + Animated.timing | 同じ animate + transition パターン — 状態駆動に変換 |
Animated.Value + Animated.spring | animate + transition={{ type: 'spring' }} — 状態駆動に変換 |
withDelay(ms, withTiming(...)) または withDelay(ms, withSpring(...)) | transition={{ ..., delay: ms }} — トランジション設定に delay を追加 |
entering={FadeIn.delay(ms)} / .delay() 付きの任意の entering プリセット | initialAnimate + animate + transition={{ ..., delay: ms }} |
useAnimatedStyle 内のプロパティごとの異なる withTiming/withSpring | transition={{ opacity: { type: 'timing', ... }, transform: { type: 'spring', ... } }} (プロパティごとマップ) |
Default Value Mapping(デフォルト値マッピング)
重要: Reanimated と EaseView には異なるデフォルトがあります。元のアニメーション動作を保持するために、値を明示的に設定する必要があります。EaseView のデフォルトが Reanimated のデフォルトと一致することに依存しないでください。
フェーズ 1 の reanimatedVersion を使用して正しいデフォルトを選択してください。
withSpring → EaseView spring
Reanimated v2/v3 デフォルト:
| パラメータ | Reanimated v2/v3 | EaseView デフォルト | アクション |
|---|---|---|---|
damping | 10 | 15 | damping: 10 を設定する必要があります |
stiffness | 100 | 120 | stiffness: 100 を設定する必要があります |
mass | 1 | 1 | 同じ — 省略 |
Reanimated v4 デフォルト:
| パラメータ | Reanimated v4 | EaseView デフォルト | アクション |
|---|---|---|---|
damping | 120 | 15 | damping: 120 を設定する必要があります |
stiffness | 900 | 120 | stiffness: 900 を設定する必要があります |
mass | 4 | 1 | mass: 4 を設定する必要があります |
Reanimated v4 は、臨界制動、スナッピースプリング(跳ね返りなし)をデフォルトに変更しました。古い物理ベースのデフォルトが開始/終了条件に対して敏感すぎるという理由からです。v4 は生の物理パラメータの代わりに duration + dampingRatio を使用することを推奨しています。
ソースコードが明示的にこれらの値のいずれかを設定している場合、そのままキャリーオーバーします。ソースが Reanimated のデフォルトに依存している場合(明示的な値なし)、Reanimated のデフォルトを EaseView トランジションで明示的に設定します。
例 — 設定なしの素の withSpring(1):
// Before (Reanimated)
scale.value = withSpring(1);
// After (EaseView) — v2/v3: set damping: 10, stiffness: 100
transition={{ type: 'spring', damping: 10, stiffness: 100 }}
// After (EaseView) — v4: set damping: 120, stiffness: 900, mass: 4
transition={{ type: 'spring', damping: 120, stiffness: 900, mass: 4 }}
期間ベーススプリング: Reanimated v3+ は withSpring(target, { duration, dampingRatio }) もサポートしています。コードが明示的に dampingRatio/duration を設定している場合、以下を使用して変換します: damping = dampingRatio * 2 * sqrt(stiffness * mass)。
withTiming → EaseView timing
| パラメータ | Reanimated デフォルト | EaseView デフォルト | アクション |
|---|---|---|---|
duration | 300 | 300 | 同じ — 省略 |
easing | Easing.inOut(Easing.quad) | 'easeInOut' (キュービック) | easing: [0.455, 0.03, 0.515, 0.955] を設定する必要があります |
イージング曲線は異なります!Reanimated のデフォルトは二次イージングイン・アウト、EaseView のはキュービックです。ソースが指定しない場合は常にイージングを明示的に設定してください。
例 — 設定なしの素の withTiming(1):
// Before (Reanimated)
opacity.value = withTiming(1);
// After (EaseView) — must set quad easing to match
transition={{ type: 'timing', duration: 300, easing: [0.455, 0.03, 0.515, 0.955] }}
ソースが明示的にイージングを設定している場合、上記のイージング表を使用してマップします。
Animated.timing (古い RN API) → EaseView timing
| パラメータ | RN Animated デフォルト | EaseView デフォルト | アクション |
|---|---|---|---|
duration | 500 | 300 | duration: 500 を設定する必要があります |
easing | Easing.inOut(Easing.ease) | 'easeInOut' | 同じ曲線 — 省略 |
Animated.spring (古い RN API) → EaseView spring
RN Animated はデフォルトで friction/tension を使用します: friction: 7, tension: 40。これらは以下にマップされます: stiffness = tension, damping = friction。
| パラメータ | RN Animated デフォルト | EaseView デフォルト | アクション |
|---|---|---|---|
| stiffness (tension) | 40 | 120 | stiffness: 40 を設定する必要があります |
| damping (friction) | 7 | 15 | damping: 7 を設定する必要があります |
| mass | 1 | 1 | 同じ — 省略 |
Unit Conversions(単位変換)
- Rotation(回転): Reanimated はトランスフォーム内で
'45deg'文字列を使用 → EaseView は45(数値、度)を使用します。'deg'サフィックスを削除して数値にパースします。 - Translation(移動): 両方とも DIPs(密度非依存ピクセル)を使用します。変換は不要です。
- Scale(スケール): 両方とも単位なしの乗数を使用します。変換は不要です。
Phase 3: Dry-Run Report(ドライランレポート)
ユーザーにコンポーネント選択を求める前に、このレポートを常に出力してください。ユーザーがフェーズ 4 に進む前にこのレポートが見える必要があります。
構造化されたレポートを出力します。まだ変更を適用しないでください。
形式:
## Migration Report
### Summary
- Files scanned: X
- Components with animations: Y
- Migratable: Z | Not migratable: W
### Migratable Components
#### `path/to/file.tsx` — ComponentName
**Current:** アニメーションが何をするか、どの API を使用するかの簡潔な説明
**Proposed:** EaseView 同等物がどのように見えるか(マップされたデフォルト値を含む正確なトランジション値を含める)
**Changes:** 何が追加/削除/変更されるか
**Note:** (該当する場合のみ)"終了アニメーション用に状態変更が必要" または他の注意事項
### Not Migratable (will be skipped)
#### `path/to/file.tsx` — ComponentName
**Reason:** 移行できない理由(決定木から)
このレポートはテキスト出力として会話内に出力する必要があります — プラン内に含めないでください、折りたたまないでください。ユーザーはフェーズ 4 でコンポーネントを選択する前にこれを読む必要があります。
Phase 4: User Confirmation(ユーザー確認)
重要: ここで AskUserQuestion ツールを使用する必要があります。プランモードを使用しないでください、インラインテキストプロンプトを使用しないでください、インラインで聞かないでください。直接 AskUserQuestion ツールを呼び出してください。
以下の正確なパラメータで AskUserQuestion を呼び出します:
multiSelect:truequestions: 以下を含む単一の質問オブジェクト:header:"Migrate"question:"Which components should be migrated to EaseView? All are selected — deselect any to skip."multiSelect:trueoptions: 移行可能なコンポーネントごとに 1 つのエントリ:label: コンポーネント名(例:"AnimatedButton")description: ファイルパスとアニメーションの簡潔な説明(例:"src/components/animated-button.tsx — spring scale on press")
2 つの移行可能なコンポーネントのツール呼び出し例:
{
"questions": [
{
"header": "Migrate",
"question": "Which components should be migrated to EaseView? All are selected — deselect any to skip.",
"multiSelect": true,
"options": [
{
"label": "AnimatedButton",
"description": "src/components/simple/animated-button.tsx — spring scale on press"
},
{
"label": "Collapsible",
"description": "src/components/ui/collapsible.tsx — fade-in entering animation"
}
]
}
]
}
ユーザーの応答を待ってから進んでください。 プランモードに入らないでください。ユーザーがコンポーネントを選択しないで変更を適用しないでください。
ユーザーが何も選択しないか、"Other" を選択してキャンセルした場合、以下で中止します: "Migration aborted. No changes were made."
確認したコンポーネントのみでフェーズ 5 に進みます。
Phase 5: Apply Migrations(移行を適用)
確認された各コンポーネントについて、移行を適用します:
Migration Steps(移行ステップ)(コンポーネントごと)
-
EaseView インポートを追加します (まだ存在しない場合):
import { EaseView } from 'react-native-ease';
1b. usesNativeWind が true の場合, import 'react-native-ease/nativewind' がプロジェクト内で既に存在するかチェックします(すべてのファイルを検索)。存在しない場合、アプリのルートエントリーポイント(例: _layout.tsx、App.tsx、または index.tsx — 最初のエントリ)に追加します。これは移行ごとではなく、全移行で 1 回だけ実行する必要があります。
-
アニメーション化されたビューを置き換えます:
Animated.View→EaseView<Animated.View style={[styles.box, animatedStyle]}>→<EaseView style={styles.box} animate={{ ... }} transition={{ ... }}>
-
アニメーションフックをプロパティに変換します:
useSharedValue、useAnimatedStyle、withTiming、withSpring、withRepeatの呼び出しを削除- それらの値を
animate、initialAnimate、およびtransitionプロパティに変換
-
entering/exiting アニメーションを変換します:
-
entering={FadeIn}→ EaseView のinitialAnimate={{ opacity: 0 }}+animate={{ opacity: 1 }} -
exitingの場合: 状態変数とonTransitionEndコールバックを導入します:const [visible, setVisible] = useState(true); const [mounted, setMounted] = useState(true); // 終了をトリガーする場合: setVisible(false); // EaseView の場合: { mounted && ( <EaseView animate={{ opacity: visible ? 1 : 0 }} transition={{ type: 'timing', duration: 300 }} onTransitionEnd={({ finished }) => { if (finished && !visible) setMounted(false); }} > ... </EaseView> ); }
-
-
インポートをクリーンアップします:
- ファイル内で使用されなくなった Reanimated インポートを削除
- 同じファイル内の移行されていないコードで参照されている Reanimated インポートをキープ
- 使用されているインポートを削除しない
-
進捗を出力します:
[1/N] Migrated ComponentName in path/to/file.tsx
Safety Rules(安全ルール)
これらのルールは譲歩できません。違反するとユーザーコードが破損します。
- 疑わしい場合はスキップします。 パターンが曖昧であるか、移行に確信がない場合は、"移行不可" に追加し、理由: "複雑なパターン — 手動レビューを推奨"
- ファイル内で使用されているインポートを削除しないでください。 アニメーションコードを削除した後、各インポートを削除する前に、残りのすべての行で参照を確認してください。
- 非アニメーションロジックをすべて保持します。 イベントハンドラー、状態管理、効果、コールバック — アニメーション移行に直接関連していない限り、何も触らないでください。
- コンポーネント構造と公開 API を保持します。 プロパティ、ref フォワーディング、エクスポートされた型 — それらを同一に保ちます。
- 混合ファイルを正しく処理します。 ファイルが移行可能と移行不可の両方のアニメーションを持っている場合、安全なもののみ移行します。残りの Reanimated コードがある場合は Reanimated インポートをキープします。
- 回転単位を正しくマップします。 Reanimated
'45deg'文字列 → EaseView45数値。ソースがラジアンを使用する場合は変換します:radians * (180 / Math.PI)。 - イージングプリセットを正しくマップします。 フェーズ 2 のマッピング表を参照してください。
- TypeScript エラーを導入しないでください。 移行後、すべての型が正しいことを確認します。元のコードが型付きシェアード値を使用する場合、EaseView プロパティが一致することを確認します。
Phase 6: Final Report(最終レポート)
すべての移行を適用した後、以下を出力します:
## Migration Complete
### Changed (X components)
- `path/to/file.tsx` — ComponentName: 何が移行されたかの簡潔な説明
### Unchanged (Y components)
- `path/to/file.tsx` — ComponentName: スキップされた理由
### Next Steps
- アプリを実行してアニメーションを視覚的に確認してください
- テストスイートを実行して回帰をチェックしてください
- Reanimated コードが残っていない場合、依存関係から `react-native-reanimated` を削除することを検討してください
EaseView API Reference(移行精度用)
Supported Animatable Properties(サポートされたアニメート化可能プロパティ)
animate プロパティ内のすべてのプロパティ:
| プロパティ | 型 | デフォルト | 注記 |
|---|---|---|---|
opacity | number | 1 | 0–1 範囲 |
translateX | number | 0 | DIPs(密度非依存ピクセル)内 |
translateY | number | 0 | DIPs 内 |
scale | number | 1 | scaleX + scaleY の短縮 |
scaleX | number | 1 | X 軸に対して scale をオーバーライド |
scaleY | number | 1 | Y 軸に対して scale をオーバーライド |
rotate | number | 0 | Z 軸回転(度) |
rotateX | number | 0 | X 軸回転(度、3D) |
rotateY | number | 0 | Y 軸回転(度、3D) |
borderRadius | number | 0 | ピクセル内 |
backgroundColor | ColorValue | 'transparent' | 任意の RN 色値 |
borderWidth | number | 0 | ピクセル内 |
borderColor | ColorValue | 'black' | 任意の RN 色値 |
shadowOpacity | number | 0 | 0–1 (iOS のみ) |
shadowRadius | number | 0 | ピクセル内(iOS のみ) |
shadowColor | ColorValue | 'black' | 任意の RN 色値(iOS のみ) |
shadowOffset | object | {width:0,height:0} | { width, height } (iOS のみ) |
elevation | number | 0 | Android マテリアルシャドウ |
Transition Types(トランジションタイプ)
Timing:
transition={{
type: 'timing',
duration: 300, // ms, default 300
easing: 'easeInOut', // 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | [x1,y1,x2,y2]
delay: 0, // ms, default 0
loop: 'repeat', // 'repeat' | 'reverse' — initialAnimate が必要
}}
Spring:
transition={{
type: 'spring',
damping: 15, // default 15
stiffness: 120, // default 120
mass: 1, // default 1
delay: 0, // ms, default 0
}}
None (即座):
transition={{ type: 'none' }}
Key Props(キープロップ)
animate— アニメーション化されたプロパティのターゲット値initialAnimate— 開始値(マウント時にanimateにアニメート化)transition— アニメーション設定: 単一のSingleTransition(timing/spring/none) またはTransitionMap(カテゴリキーdefault、transform、opacity、borderRadius、backgroundColor、border、shadow)onTransitionEnd—{ finished: boolean }を含むコールバックtransformOrigin— ピボットポイント{ x: 0-1, y: 0-1 }(デフォルト中央)useHardwareLayer— Android GPU 最適化(ブール値、デフォルト false)className— NativeWind / Tailwind CSS クラス文字列(プロジェクトで NativeWind が必要)
Important Constraints(重要な制約)
- ループはタイミングが必要 (スプリングではない)でき
initialAnimateで開始値を定義する必要があります - プロパティごとのトランジションがサポートされています — カテゴリキー(
default、transform、opacity、borderRadius、backgroundColor、border、shadow)を持つTransitionMapを渡して、プロパティグループごとに異なるコンフィグを使用します - アニメーションシーケンシングなし —
withSequenceの同等物なし。シンプルなwithDelayはdelayトランジションプロップ経由でサポートされています - ジェスチャー/スクロール駆動アニメーションなし — EaseView は状態駆動のみ
- スタイル/アニメート競合 — プロパティが
styleとanimateの両方に表示される場合、アニメーション化された値が優先
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- appandflow
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/appandflow/react-native-ease / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。