react-native-ease-refactor について
react-native-ease refactor は、対応する React Native Reanimated と Animated のコードを react-native-ease の EaseView コンポーネントへ移行するスキルです。アニメーションを調査して移行可否を報告し、変更前に確認を求めます。
主な機能
- 01Reanimated と Animated の使用箇所を調査
- 02移行可能なアニメーションパターンを分類
- 03タイミング・スプリング・イージング設定を変換
- 04非対応パターンと理由を報告
- 05編集前に対象コンポーネントを選択
活用シーン
- 01状態駆動アニメーションを EaseView に移行
- 02移行範囲の選択前にアニメーションコードを確認
- 03移行できないジェスチャーやスクロール駆動を特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ appandflow/ react-native-ease --skill react-native-ease-refactorインストール後、Claude Code で/react-native-ease-refactorを入力して読み込まれたか確認します。
詳細
react-native-ease のリファクタリング
react-native-reanimated および React Native 組み込みの Animated API のコードを、react-native-ease の EaseView コンポーネントに変換する移行アシスタントです。
以下の6つのフェーズを、必ず指定の順序どおりに実行してください。フェーズを省略したり、順序を入れ替えたりしないでください。
Phase 1: 検出
ユーザーのプロジェクトをスキャンして、アニメーションコードを検出します。
-
Grep を使用して、プロジェクトで NativeWind が使用されているかを検出します。
- パターン:
from ['"]nativewind['"](**/*.{ts,tsx,js,jsx}が対象) package.jsonの依存関係に"nativewind"が含まれているかも確認します- NativeWind が検出された場合は、Phase 5 で使用するフラグ
usesNativeWind = trueを設定します
- パターン:
-
Reanimated のバージョンを検出します(Phase 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}を検索します
- パターン:
-
Grep を使用して、React Native 組み込みの
AnimatedAPI を使用しているファイルをすべて検索します。- パターン:
from ['"]react-native['"]を含み、同時にAnimatedも使用しているファイル - パターン:
Animated\.View|Animated\.Text|Animated\.Image|Animated\.Value|Animated\.timing|Animated\.spring
- パターン:
-
Grep を使用して、すでに
react-native-easeを使用しているファイルを検索します(再移行を避けるため)。- パターン:
from ['"]react-native-ease['"]
- パターン:
-
アニメーションコードを含む各ファイルを読み込みます。コンポーネントと、そのアニメーションパターンの一覧を作成します。
スキャン対象から除外するもの:
node_modules/*.test.*および*.spec.*ファイル- ビルド出力ディレクトリ(
lib/、build/、dist/)
Phase 2: 分類
検出した各コンポーネントを、移行可能または移行不可に分類します。
判定ツリー
以下のチェックを順番に適用します。最初に該当した項目で結果を決定します。
- ジェスチャー API を使用していますか?(
Gesture.Pan、Gesture.Pinch、Gesture.Rotation、useAnimatedGestureHandler)→ 移行不可 — 「ジェスチャー駆動のアニメーション」 - スクロールハンドラーを使用していますか?(
useAnimatedScrollHandler、Animated.eventを使ったonScroll)→ 移行不可 — 「スクロール駆動のアニメーション」 - 共有要素トランジションを使用していますか?(
sharedTransitionTag)→ 移行不可 — 「共有要素トランジション」 runOnUIまたはワークレットディレクティブを使用していますか? → 移行不可 — 「ワークレットランタイムが必要」withSequenceを使用していますか? → 移行不可 — 「アニメーションのシーケンスはサポートされていません」 5b. 単一のアニメーション(withTiming/withSpring)をラップするwithDelayを使用していますか? → 移行可能 — トランジションのdelayに割り当てます 5c.withSequenceまたはネストされたwithDelayをラップする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の timing、scale に spring を使用)→ 移行可能 — カテゴリーキー(
transform、opacity、borderRadius、backgroundColor、border、shadow、default)を使ったTransitionMapに割り当てます - 状態によって駆動されていませんか?(アニメーションが React の状態ではなく、ジェスチャー/スクロール値によってトリガーされる)→ 移行不可 — 「状態駆動ではありません」
- 上記のいずれにも該当しない場合 → 移行可能
移行可能なパターンの対応付け
以下の表を使用して、Reanimated/Animated のパターンを EaseView に変換します。
| Reanimated / Animated のパターン | EaseView での同等の書き方 |
|---|---|
不透明度、移動、拡大縮小、回転、borderRadius、backgroundColor には useSharedValue + useAnimatedStyle + withTiming | animate={{ prop: value }} + transition={{ type: 'timing', duration, easing }} |
withSpring | transition={{ type: 'spring', damping, stiffness, mass, velocity }} |
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 を追加します |
一時停止してから sv.value から再開する cancelAnimation(sv) | paused プロパティ(同じ位置から再開します);一時停止時の値は onPaused から読み取ります |
entering={FadeIn.delay(ms)} / .delay() を持つ任意の entering プリセット | initialAnimate + animate + transition={{ ..., delay: ms }} |
useAnimatedStyle 内でプロパティごとに異なる withTiming / withSpring | transition={{ opacity: { type: 'timing', ... }, transform: { type: 'spring', ... } }}(プロパティごとのマップ) |
デフォルト値のマッピング
重要:Reanimated と EaseView ではデフォルト値が異なります。元のアニメーション動作を維持するには、値を明示的に設定する必要があります。EaseView のデフォルト値が Reanimated のデフォルト値と一致すると想定しないでください。
Phase 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 | 同じ — 省略 |
velocity | 0 | 0 | 同じ — 設定されている場合のみ引き継ぐ |
Reanimated v4 のデフォルト値:
| パラメーター | Reanimated v4 | EaseView のデフォルト値 | 対応 |
|---|---|---|---|
damping | 120 | 15 | damping: 120 を設定する必要があります |
stiffness | 900 | 120 | stiffness: 900 を設定する必要があります |
mass | 4 | 1 | mass: 4 を設定する必要があります |
velocity | 0 | 0 | 同じ — 設定されている場合のみ引き継ぐ |
Reanimated v4 では、デフォルトが臨界減衰の、キレのあるスプリング(バウンスなし)に変更されました。従来の物理ベースのデフォルト値は、開始条件と終了条件の影響を受けすぎることが変更の理由です。v4 では、生の物理パラメーターの代わりに duration + dampingRatio を使用することが推奨されています。
ソースコードでこれらの値が明示的に設定されている場合は、そのまま引き継いでください。明示的な値がなく、ソースが Reanimated のデフォルト値に依存している場合は、EaseView のトランジションに Reanimated のデフォルト値を明示的に設定してください。
例 — 設定なしの 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 }}
duration ベースの spring: 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'(cubic) | 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 | 同じ — 省略 |
| velocity | 0 | 0 | 同じ — 設定されている場合のみ引き継ぐ |
Animated.spring では、friction/tension の代わりに bounciness/speed も指定できます。これらは
SpringConfig.fromBouncinessAndSpeed を通じて処理されるため、数値を直接マッピングせず、この関数で変換してください — デフォルトの bounciness: 0, speed: 12 は
{ stiffness: 342.1, damping: 36.93 } に変換されます。
単位の変換
- 回転: Reanimated では変換に
'45deg'文字列を使用します → EaseView では45(数値、度)を使用します。'deg'の接尾辞を削除して数値に解析してください。 - 移動: どちらも DIP(密度非依存ピクセル)を使用します。変換は不要です。
- スケール: どちらも単位のない倍率を使用します。変換は不要です。
- スプリングの速度: アニメーション対象のプロパティと同じ単位を毎秒単位で使用します。移動は DIP、回転は度、スケールと不透明度は単位なしです。変換は不要ですが、速度は遷移ごとに設定するため、移動とスケールで共有する
transform遷移には、意味のある速度を 1 つしか設定できない点に注意してください。両方に速度が必要な場合は、カテゴリを分けてください。
フェーズ 3:ドライランレポート
コンポーネントの選択をユーザーに求める前に、必ずこのレポートを出力してください。このレポートはフェーズ 4 の前にユーザーが確認できる状態にする必要があります。
構造化されたレポートを出力してください。まだ変更は適用しないでください。
形式:
## Migration Report
### Summary
- Files scanned: X
- Components with animations: Y
- Migratable: Z | Not migratable: W
### Migratable Components
#### `path/to/file.tsx` — ComponentName
**Current:** Brief description of what the animation does and which API it uses
**Proposed:** What the EaseView equivalent looks like (include exact transition values with mapped defaults)
**Changes:** What will be added/removed/modified
**Note:** (only if applicable) "Requires state changes for exit animation" or other caveats
### Not Migratable (will be skipped)
#### `path/to/file.tsx` — ComponentName
**Reason:** Why it can't be migrated (from decision tree)
このレポートは、プラン内や折りたたまれた状態ではなく、会話内のテキスト出力として必ず表示してください。フェーズ 4 でコンポーネントを選択する前に、ユーザーが内容を確認する必要があります。
フェーズ 4:ユーザーによる確認
重要:ここでは必ず AskUserQuestion ツールを使用してください。プランモードやテキストプロンプトは使用せず、インラインで質問しないでください。AskUserQuestion ツールを直接呼び出してください。
次の正確なパラメーターを指定して AskUserQuestion を呼び出してください。
multiSelect:truequestions:次の内容を含む質問オブジェクトを 1 つ指定します。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」を選択してキャンセルした場合は、「移行を中止しました。変更は行われていません。」として中止してください。
ユーザーが確認したコンポーネントのみを対象に、フェーズ 5 へ進んでください。
フェーズ 5:移行の適用
確認済みの各コンポーネントについて、移行を適用してください。
移行手順(コンポーネントごと)
-
EaseView のインポートを追加(まだ存在しない場合):
import { EaseView } from 'react-native-ease';
1b. usesNativeWind が true の場合、import 'react-native-ease/nativewind' がプロジェクト内にすでに存在するか確認します(すべてのファイルを検索)。存在しない場合は、アプリのルートエントリポイント(例:_layout.tsx、App.tsx、または index.tsx のうち、最も早いエントリ)に追加します。これはすべての移行を通じて一度だけ行えばよく、コンポーネントごとに行う必要はありません。
-
アニメーションビューを置き換えます:
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); // When triggering exit: setVisible(false); // On the 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
安全ルール
これらのルールは絶対です。違反するとユーザーのコードが破損します。
- 迷ったらスキップします。 パターンが曖昧であるか、移行に自信がない場合は、「Not Migratable」に理由「Complex pattern — manual review recommended」を付けて追加します
- ファイル内の他の場所でまだ使用されているインポートは決して削除しないでください。 アニメーションコードを削除した後、削除する前に各インポートへの参照が残りのすべての行にないか確認します。
- アニメーション以外のロジックをすべて保持します。 イベントハンドラー、状態管理、エフェクト、コールバック — 移行対象のアニメーションに直接関係しない限り、一切触れないでください。
- コンポーネント構造と公開 API を保持します。 props、ref のフォワーディング、エクスポートされた型 — それらを同一に保ちます。
- 混在ファイルを正しく処理します。 ファイルに移行可能なアニメーションと移行不可能なアニメーションの両方がある場合は、安全なものだけを移行します。Reanimated のコードが残っている場合は、Reanimated のインポートを保持します。
- 回転の単位を正しくマッピングします。 Reanimated の
'45deg'文字列 → EaseView の45数値。ソースがラジアンを使用している場合は変換します:radians * (180 / Math.PI)。 - イージングプリセットを正しくマッピングします。 フェーズ 2 のマッピングテーブルを参照してください。
- TypeScript エラーを発生させないでください。 移行後にすべての型が正しいことを確認します。元のコードが型付き共有値を使用している場合は、EaseView のプロパティが一致することを確認します。
フェーズ 6:最終レポート
すべての移行が適用された後、出力します:
## Migration Complete
### Changed (X components)
- `path/to/file.tsx` — ComponentName: brief description of what was migrated
### Unchanged (Y components)
- `path/to/file.tsx` — ComponentName: reason skipped
### Next Steps
- Run your app and verify animations visually
- Run your test suite to check for regressions
- If no Reanimated code remains, consider removing `react-native-reanimated` from dependencies
EaseView API リファレンス(移行の正確性のため)
アニメーション可能なプロパティ
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={{
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' — requires initialAnimate
}}
スプリング:
transition={{
type: 'spring',
damping: 15, // default 15
stiffness: 120, // default 120
mass: 1, // default 1
velocity: 0, // property units/s, default 0, native only
delay: 0, // ms, default 0
}}
なし(即時):
transition={{ type: 'none' }}
主な Props
animate— アニメーション対象プロパティの目標値initialAnimate— 開始値(マウント時にanimateの値までアニメーションします)transition— アニメーション設定:単一のSingleTransition(timing/spring/none)、またはカテゴリキー(default、transform、opacity、borderRadius、backgroundColor、border、shadow)を持つTransitionMaponTransitionEnd—{ finished: boolean }を受け取るコールバックpaused— 実行中のアニメーション(ループを含む)をその場で一時停止します。falseにすると同じ位置から再開しますonPaused—pausedが有効になった時点でのanimateのキーの画面上の値である{ values }を受け取るコールバックtransformOrigin—{ x: 0-1, y: 0-1 }で指定する回転・変形の基点。デフォルトは中央useHardwareLayer— Android の GPU 最適化(ブール値、デフォルトは false)className— NativeWind / Tailwind CSS のクラス文字列(プロジェクトに NativeWind が必要です)
重要な制約
- ループには timing が必要です(spring は不可)。また、
initialAnimateで開始値を定義する必要があります - プロパティごとのトランジションに対応 — カテゴリキー(
default、transform、opacity、borderRadius、backgroundColor、border、shadow)を持つTransitionMapを渡すと、プロパティグループごとに異なる設定を使用できます - アニメーションのシーケンスには非対応 —
withSequenceに相当する機能はありません。withDelayはdelayトランジションプロパティで使用できます - ジェスチャーやスクロールに連動するアニメーションには非対応 — EaseView は状態駆動のみです
- Style と animate の競合 — プロパティが
styleとanimateの両方にある場合、アニメーション値が優先されます