---
title: "react-springの使い方｜useSpring・useTrail・useChainをv10系で実装する"
url: "https://www.issoh.co.jp/tech/details/4256/"
published: 2024-11-18
updated: 2026-09-27
categories: ["React"]
publisher: "株式会社一創"
---

# react-springの使い方｜useSpring・useTrail・useChainをv10系で実装する

react-springはバネの物理モデルで値を補間するReact用アニメーションライブラリで、**2026年8月時点の最新版は@react-spring/web 10.1.2**（2026年6月24日公開）です。日本語の解説記事の多くはv9時代に書かれており、React 19対応が入ったv10.0.0や、アンブレラパッケージを廃止するv11 betaが反映されていません。この記事ではuseSpring・useSprings・useTrail・useChainの実装を、React 19.2.8とv10.1.2を実際に動かして確認した挙動つきで整理します。**本文の実測値は、いずれもReact 19.2.8＋@react-spring/web 10.1.2をjsdom上で実行して取得したものです**（requestAnimationFrameの刻み方は実ブラウザと異なるため、加速中の中間値は環境によって数ポイント動きます）。

## まとめ：v10.1.2でreact-springを実装するときの要点

- Webだけで使うなら`@react-spring/web`を入れる。アンブレラの`react-spring`はkonva・three・zdog・nativeまで依存に引き込む。
- React 19で使うならv10以上が必須。v9.7.5のpeerDependenciesはReact 18までで、React 19が含まれない。
- `useSpring`は**オブジェクトを渡すとスタイル値だけ、関数（または第2引数の依存配列）を渡すと\[スタイル値, API\]のタプル**が返る。api.startを使いたいなら関数形式で書く。
- `useChain`の連結はdelayでは制御できない。`useSpringRef`で作ったrefを渡す`useChain(refs, timeSteps, timeFrame)`が正しい書き方。
- `useReducedMotion`は真偽値を返すだけでなく、OS側の視差効果低減設定を検知してアプリ全体のアニメーションを停止させる。初回レンダーの戻り値はtrueでもfalseでもなくnull。
- v11.0.0-beta.0ではアンブレラパッケージの廃止とESM専用化という破壊的変更が入っている。新規実装は`@react-spring/web`からの名前付きimportで書いておくと移行が軽い。
- react-motionはnpm最新版が0.5.2（2017年10月3日公開）で更新が止まっており、React 19時代の選択肢には残らない。

以下、パッケージ選定、useSpring、複数要素（useSprings・useTrail・useTransition）、useChainによる連結、スクロール連動と可視判定、アクセシビリティ、react-motionからの移行の順に、実行して確かめた挙動つきで見ます。

## v10.1.2時点のパッケージ構成と対応Reactバージョン

CSSトランジションが「0.3秒かけてイージング曲線をたどる」という時間指定の考え方をとるのに対し、react-springは張力（tension）と摩擦（friction）を持つバネの運動をフレームごとに解き、現在の速度を引き継ぎながら目標値へ収束させます。途中で目標値が変わっても速度が連続するため、ドラッグやホバーのように入力が途切れず変化する操作と相性がよい設計です。

比較検討されやすいframer-motionとは前提が違います。framer-motionはレイアウト変化の自動補間（layoutプロパティ）や宣言的なvariantsによる状態遷移が中心で、react-springはバネの物理パラメータを直接触らせる設計です。レイアウトの入れ替わりを自動で滑らかにしたいならframer-motion、tensionとfrictionで動きの質感を作り込みたいならreact-springを選ぶ、という住み分けになります。なおプロジェクト名は2024年に「Motion」へ改称され、npmでも`framer-motion`と`motion`が同じ13.1.0（2026年8月10日公開）で並存しています。

混同しやすいのがフック名です。**framer-motion側にも`useSpring`という同名のフックがありますが、こちらはMotionValueか数値を第1引数に取る別物**で、`useSpring(0, { stiffness: 300 })`のように書きます。react-springの`useSpring({ from, to })`とは引数も戻り値も違うため、サンプルコードを混ぜて写すと動きません。framer-motion側の書き方は[Framer Motion（現Motion）のmotionコンポーネントとイージング](/tech/details/3016/)で整理しています。

### @react-spring/webとアンブレラreact-springの依存差

npmには`react-spring`（アンブレラ）と`@react-spring/web`の2つが存在します。npm registryで確認すると、アンブレラ版10.0.4のdependenciesは`@react-spring/core`・`@react-spring/konva`・`@react-spring/native`・`@react-spring/three`・`@react-spring/web`・`@react-spring/zdog`の6つで、Webしか使わない場合もKonva・Three.js・React Native向けのアダプタまで解決対象に入ります。ブラウザ向けの実装だけが目的なら、ターゲット別パッケージを直接入れてください。

```
npm install @react-spring/web
```

後述のとおりv11ではアンブレラパッケージ自体が削除される予定のため、新規プロジェクトでアンブレラ版を選ぶ理由はほぼありません。

### React 19を使うならv10以上が必須という境界

peerDependenciesを版ごとに引くと境界がはっきりします。v9系の最終版である9.7.5（2024年10月7日公開）は`react: ^16.8.0 || ^17.0.0 || ^18.0.0`で、React 19が範囲に含まれません。React 19対応はv10.0.0（2025年5月15日公開）でPR #2368として入り、10.1.2では`react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0`になっています。React 19へ上げたときにpeer依存の警告が出るなら、react-spring側がv9のままである可能性を先に疑ってください。

React 19系のプロジェクト構成そのものを見直している段階なら、[React Compilerの自動メモ化とuseMemoとの違い](/tech/details/9415/)も合わせて確認しておくと、アニメーション周りの再レンダリング対策の重複を避けられます。

### v11.0.0-beta.0で予告されている破壊的変更

v11.0.0-beta.0のリリースノートが2026年6月24日に公開されています（npmへのpublish自体は6月22日で、確認する場所によって2日ずれます。いずれも日本時間換算）。破壊的変更を伴うPRは2本です。1つはPR #2526で、非推奨ターゲット・React Native対応・react-springアンブレラパッケージの3つを削除します。もう1つはPR #2544による「ESM専用ビルドへの移行」です。実際、npm registryのアンブレラ`react-spring`には11系のバージョンが1つも公開されておらず、latestは10.0.4のまま止まっています。

この2点は、CommonJSの`require('react-spring')`で読み込んでいるコードを直撃します。ESM専用化はアンブレラだけでなく`@react-spring/web`自体のビルドにも及ぶため、import構文がESMでも、JestなどがCommonJSへトランスパイルしている構成なら影響を受けます。Vite等のバンドラ経由でESMのまま完結しているなら影響はありません。PR #2544には移行条件も書かれており、CommonJSのまま動かすならESMパッケージの`require()`が解決されるNode 22.12以上が必要で、Webpack 4はサポート対象から外れます。

## useSpringの使い方｜呼び出し方で戻り値が変わる条件

useSpringはreact-springの基点になるフックですが、「配列で受けるサンプルとオブジェクトで受けるサンプルが混在していて、どちらが正しいのか分からない」という混乱が起きやすい箇所です。原因は仕様どおりのオーバーロードです。

### オブジェクト渡しと関数渡しで返り値が変わる仕組み

型定義上、useSpringには3つのオーバーロードがあります。第1引数が関数の場合と、第2引数に依存配列を渡した場合は`[SpringValues, SpringRef]`のタプルを返し、設定オブジェクトだけを渡した場合は`SpringValues`を単体で返します。React 19.2.8と@react-spring/web 10.1.2で実際に実行したところ、オブジェクト渡しの戻り値は配列ではなくopacityキーを持つオブジェクト、関数渡しの戻り値は長さ2の配列で、2要素目にstartメソッドを持つAPIが入っていました。

```
import { useSpring, animated } from '@react-spring/web'

// オブジェクト渡し: SpringValues だけが返る（宣言的に一度だけ動かす用途）
export function FadeIn() {
  const styles = useSpring({ from: { opacity: 0 }, to: { opacity: 1 } })
  return <animated.div style={styles}>Hello, react-spring!</animated.div>
}

// 関数渡し: [SpringValues, SpringRef] のタプルが返る（命令的に制御する用途）
export function Toggle() {
  const [styles, api] = useSpring(() => ({ opacity: 0 }))
  return (
    <div>
      <animated.div style={styles}>Hello, react-spring!</animated.div>
      <button
        onClick={() => api.start({ opacity: styles.opacity.get() === 0 ? 1 : 0 })}
      >
        Toggle
      </button>
    </div>
  )
}
```

アニメーションさせる要素は必ず`animated.div`のようにanimated経由で描画します。素の`div`にSpringValuesを渡すと、実測ではstyle属性がnullのまま変化しませんでした。

### configプリセット6種のtension・friction実数値

動きの質はconfigで決まります。バンドルからconfigオブジェクトを読み出すと、プリセットはdefault・gentle・wobbly・stiff・slow・molassesの6種で、実数値は次のとおりでした。tensionは目標へ引き戻す力、frictionは減衰で、frictionが小さいほど行き過ぎて揺り戻す動きになります。

| プリセット    | tension | friction | 体感         |
| -------- | ------- | -------- | ---------- |
| default  | 170     | 26       | 標準         |
| gentle   | 120     | 14       | ゆっくり寄る     |
| wobbly   | 180     | 12       | 大きく揺り戻す    |
| stiff    | 210     | 20       | 素早く止まる     |
| slow     | 280     | 60       | 強く引いて重く止まる |
| molasses | 280     | 120      | 粘って収束      |

UIの操作フィードバックはstiff、モーダルの出現はgentle、遊びを見せたいマイクロインタラクションはwobblyが起点になります。プリセットで足りなければ`config: { tension: 210, friction: 20 }`のように個別指定してください。

### 時間ベースへの切り替え｜duration・easingとimmediate

バネではなく「何ミリ秒で動かすか」を決めたい箇所もあります。`AnimationConfig`には`duration`と`easing`があり、型定義のコメントどおり**easingはdurationを指定したときだけ使われます**。tension・frictionと併記しても、durationがあれば時間ベースで動きます。イージング関数は`easings`からimportして渡します。

```
import { useSpring, animated, easings } from '@react-spring/web'

const styles = useSpring({
  from: { x: 0 },
  to: { x: 100 },
  config: { duration: 400, easing: easings.easeInOutCubic },
})
```

この設定で実行すると、200ミリ秒時点のxは41.7、500ミリ秒時点では100.0でした。指定した400ミリ秒で目標へ到達し、バネのような行き過ぎと収束の余韻は出ません。`easings`には`linear`を含む31種の名前付きイージング関数と、階段状に進める`easings.steps`（イージング関数を返すファクトリ）が入っています。

アニメーションを飛ばして値だけ即座に反映したいなら`immediate: true`を使います。実測では30ミリ秒時点で目標値の100に達していました。初期表示だけ動かしたくない場合や、後述のuseReducedMotionを使わずコンポーネント単位で動きを止めたい場合の逃げ道になります。

### onStart・onChange・onRestで完了後の処理をつなぐ

「アニメーションが終わってからモーダルを閉じる」「終了後にフォーカスを移す」といった後処理は、setTimeoutで秒数を推測せず、アニメーション側のコールバックで受け取ります。`onStart`・`onChange`・`onRest`をuseSpringの設定に直接書けます。

```
const [styles, api] = useSpring(() => ({
  opacity: 0,
  onStart: () => console.log('開始'),
  onChange: () => console.log('値が変化'),
  onRest: (result) => console.log('終了', result.finished, result.value.opacity),
}))
```

api.start({ opacity: 1 })を実行して発火順を記録したところ、start・change・restの順に呼ばれ、onRestの引数は`finished: true`とvalueに収束値の1を持っていました。途中で`api.stop()`を呼んで止めた場合は、**onRestがfinished: falseと停止時点の値（実測では0.48）で届きます**。後処理を走らせてよいかはこのフラグで判定してください。なお、走行中に別のstartで目標値を上書きしただけでは途中でonRestは発火せず、最終的な停止時に1回だけfinished: trueで呼ばれました。

### api.startとSpringValue.get()による命令的な更新

関数渡しで受け取ったAPIの`start`は、レンダリングを経由せずにアニメーションを開始します。現在値は`styles.opacity.get()`で同期的に読み出せるため、「いまの値を見て次の目標を決める」トグル処理を状態変数なしで書けます。上のToggleでクリック相当の処理を実行すると、0だったopacityは約0.6秒後に0.997へ到達しました（バネは漸近的に収束するため厳密な1ではありません）。

一方、開閉状態をpropsやContextで持っていて、その状態からスタイルを導出できるなら、オブジェクト渡しのほうが読みやすくなります。アプリ全体で開閉状態を共有する設計にするなら[createContextとuseContextによる状態共有](/tech/details/2681/)と組み合わせてください。

## 複数要素のアニメーション｜useSprings・useTrail・useTransitionの使い分け

どちらも要素数を第1引数に取るため混同されがちですが、役割は明確に違います。useSpringsは**要素ごとに別々の値を持たせる**ためのフック、useTrailは**同じ動きを時間差で後続へ伝播させる**ためのフックです。リストの各項目が別方向へ動くならuseSprings、同じフェードインが順番に走るならuseTrailを選びます。

### useSpringsによるインデックス別の値更新

useSpringsも第2引数が関数ならタプルを返します。重要なのは、返ってきたAPIのstartにも関数を渡せる点です。引数にインデックスが渡るため、対象の要素だけを別の値へ動かせます。3要素で偶数番だけopacityを1にする更新を実行したところ、0.7秒後の値は順に1.00、0.00、1.00となり、1番目だけが据え置かれることを確認しました。

```
import { useSprings, animated } from '@react-spring/web'

const items = ['Item 1', 'Item 2', 'Item 3']

export function Highlight() {
  const [springs, api] = useSprings(items.length, () => ({ opacity: 0 }))

  // 偶数番のカードだけを表示状態へ動かす
  const showEven = () => api.start(i => ({ opacity: i % 2 === 0 ? 1 : 0 }))

  return (
    <div>
      {springs.map((style, i) => (
        <animated.div key={i} style={style}>{items[i]}</animated.div>
      ))}
      <button onClick={showEven}>Show even</button>
    </div>
  )
}
```

### useTrailによる同一アニメーションの時間差伝播

useTrailは第1引数の要素数ぶんのSpringValuesを配列で返し、先頭の値の変化が後続を順に引っ張ります。各要素へ手作業でdelayを割り振る必要はありません。下のコードを3要素で実行すると、200ミリ秒時点のopacityは先頭から順におよそ0.7台・0.3前後・0.1未満となり、同じ設定のまま後続ほど遅れて立ち上がることが数値で確認できます（加速中の値なので実行ごとに数ポイント動きます）。

```
import { useTrail, animated } from '@react-spring/web'

const items = ['Item 1', 'Item 2', 'Item 3']

export function TrailList() {
  const trail = useTrail(items.length, {
    from: { opacity: 0, x: -20 },
    to: { opacity: 1, x: 0 },
  })

  return (
    <div>
      {trail.map((style, i) => (
        <animated.div key={i} style={style}>{items[i]}</animated.div>
      ))}
    </div>
  )
}
```

伝播の向きを変える`reverse: true`には落とし穴があります。向きが末尾から先頭に変わるだけでなく、**同時にfromとtoが入れ替わります**。上のコードにreverseを足して実測すると、0ミリ秒時点のopacityが3要素ともほぼ1.00から始まり、600ミリ秒後には0.15前後・0.04前後・0.00へ向かいました。つまり「末尾から順にフェードイン」を期待して足すと、実際にはフェードアウトになります。向きだけ反転させたいなら、fromとtoも書き換えてください。

ドラッグやリサイズの操作そのものを実装したい場合は、react-spring単体ではなく[react-rndによるドラッグとリサイズの実装](/tech/details/3532/)のように操作を担うライブラリと役割を分けたほうが、状態管理が単純になります。react-springと同じpmndrs（Poimandres）が公開している`@use-gesture/react`を組み合わせる構成も一般的で、`useDrag`が受け取った移動量をapi.startへ流し、指を離した後の慣性だけバネに任せる、という分担になります。

### useTransitionによるマウント・アンマウント時の制御

useSpringsとuseTrailは「すでに存在する要素」を動かすフックです。ここでReactのアニメーションが難しくなる理由が出てきます。状態から項目を消した瞬間にDOMごと消えるため、**フェードアウトさせようにも動かす対象がもう存在しない**のです。CSSのtransitionが消滅そのものに効かないのも同じ理由です。useTransitionはこの問題を担当するフックで、追加・更新・削除の各フェーズに別々のスタイルを割り当て、leaveのアニメーションが終わってから実際にアンマウントします。

```
import { useTransition, animated } from '@react-spring/web'

export function FadingList({ items }) {
  const transitions = useTransition(items, {
    keys: item => item.id,
    from: { opacity: 0, height: 0 },
    enter: { opacity: 1, height: 40 },
    leave: { opacity: 0, height: 0 },
  })

  return transitions((style, item) => (
    <animated.div style={style}>{item.label}</animated.div>
  ))
}
```

3件のリストから1件を取り除いて描画結果を追跡したところ、削除から120ミリ秒の時点ではDOM上の要素数が3のまま保たれ、leaveのアニメーションが終わってから2件に減りました。この遅延アンマウントがuseTransitionの本体で、useSpringsやuseTrailでは再現できない部分です。

## useChainの連結制御｜delayではなくSpringRefによる指定

useChainは連結の指定方法を取り違えやすいフックです。「delayプロパティで開始タイミングを調整する」という記述を見かけますが、これは誤りです。delayは個々のアニメーションの開始を遅らせるだけで、useChainの連結順とは無関係に動きます。

### useChain(refs, timeSteps, timeFrame)の引数と役割

@react-spring/core 10.1.2の型定義では、シグネチャは次のとおりです。

```
declare function useChain(refs: ReadonlyArray<SpringRef>, timeSteps?: number[], timeFrame?: number): void
```

第1引数は`useSpringRef()`で作ったrefの配列で、この並び順が実行順になります。各アニメーション側は`ref`プロパティでrefを受け取り、refが渡された時点で自動再生を止めてuseChainの指示待ちになります。第2引数timeStepsは0から1の比率でそれぞれの開始位置を指定し、第3引数timeFrameは全体の長さをミリ秒で与えます（省略時は1000）。つまり`[0, 0.5]`は「1本目は即座に、2本目は500ミリ秒後に開始」を意味します。

```
import { useSpring, useSpringRef, useChain, animated } from '@react-spring/web'

export function ChainedModal() {
  const boxRef = useSpringRef()
  const textRef = useSpringRef()

  const box = useSpring({ ref: boxRef, from: { x: 0 }, to: { x: 100 } })
  const text = useSpring({ ref: textRef, from: { y: 0 }, to: { y: 100 } })

  // boxRef を先に、textRef を全体の 50% 地点から開始する
  useChain([boxRef, textRef], [0, 0.5])

  return (
    <div>
      <animated.div style={box}>box</animated.div>
      <animated.div style={text}>text</animated.div>
    </div>
  )
}
```

### timeStepsを与えたときの実測タイムライン

上のコードをjsdom上でマウントして値を追跡したところ、マウントから150ミリ秒の時点でxはおおむね55〜65まで進んでいるのに対し、yは0.0のままでした。1.55秒後には両方とも100に到達しています。2本目がtimeSteps分だけ待機してから走り出していることが数値で確認できます。なお、加速中の中間値は毎フレームのスケジューリング次第で実行ごとに数ポイント動くため、小数第1位までは再現しません。収束後の値は安定します。

refを渡したのにuseChainを呼び忘れると、そのアニメーションは**一度も再生されません**。型定義のJSDocにも、refが定義されているときは自動再生しない旨が明記されています。「refをつけた瞬間に動かなくなった」という症状は、まずこの呼び忘れを疑ってください。

## スクロール連動と可視判定に使う周辺フック

@react-spring/web 10.1.2のエクスポートには、useSpring系のほかに`useInView`・`useScroll`・`useResize`・`useSpringValue`・`useReducedMotion`が含まれています。IntersectionObserverやscrollイベントを自前で書かなくても、スクロール位置や可視判定をバネの値として受け取れます。

### useInViewの2つのオーバーロード｜真偽値かスプリングか

useInViewは型定義でオーバーロードが2本用意されています。引数なし（またはIntersectionArgsだけ）で呼ぶと`[RefObject<any>, boolean]`が返り、可視状態を真偽値で受け取ります。**第1引数にスプリングを返す関数を渡すと`[RefObject<any>, SpringValues]`が返り、可視になった時点で走るアニメーションをその場で組めます**。「画面に入ったらフェードイン」は後者のほうが短く、useSpringを別に呼ぶ必要がありません。

```
import { useInView, animated } from '@react-spring/web'

export function FadeInOnScroll() {
  const [ref, styles] = useInView(
    () => ({ from: { opacity: 0 }, to: { opacity: 1 } }),
    { once: true }
  )
  return <animated.div ref={ref} style={styles}>見えたら表示</animated.div>
}
```

IntersectionObserverを差し替えて実行したところ、交差前のopacityは0で、交差を通知した後は1.00へ到達しました。引数なしで呼んだ側は同じタイミングでfalseからtrueへ変わります。`once: true`を付けると一度可視になった時点で監視を解除するため、スクロールで往復しても再生は1回だけです。

### Next.js App Routerで必要な’use client’指定

これらはいずれもブラウザAPIを購読するクライアント専用のフックです。Next.jsのApp Routerで使う場合は、呼び出し元のファイル先頭に`'use client'`が必要になります。サーバーコンポーネントのまま置くとビルド時にエラーになります。

## useReducedMotionの副作用｜真偽値の取得ではなく全体停止のスイッチ

useReducedMotionは名前から真偽値を返すだけのフックに見えますが、呼んだ時点でライブラリ全体の設定を書き換えます。「値を取得するだけ」のつもりで置くと、想定より広い範囲に影響します。

@react-spring/shared 10.1.2の実装を読むと、このフックは`window.matchMedia('(prefers-reduced-motion)')`を購読し、変化のたびに`Globals.assign({ skipAnimation: e.matches })`を呼びます。Globalsはライブラリ全体で共有される設定なので、**1つのコンポーネントでuseReducedMotionを呼ぶと、アプリ内の全アニメーションが一括で停止対象になります**。呼び出し元のコンポーネントだけに閉じた効果ではありません。

この副作用にはさらに2つの癖があります。1つは順序依存で、skipAnimationを立てるのはレイアウトエフェクトなので、**useReducedMotionを呼ぶコンポーネントより先にエフェクトが走る位置のアニメーションは止まりません**（実測でも、先にマウントした側は通常どおり再生されました）。もう1つは後始末で、実装のクリーンアップはmatchMediaのリスナーを外すだけで`skipAnimation`を元に戻しません。**呼び出し元がアンマウントされた後も全体停止が残り続けます**。

もう1つの注意点は初期値です。実装では`useState(null)`で始まり、matchMediaの購読はレイアウトエフェクト内で行われます。つまり初回レンダー時の戻り値はtrueでもfalseでもなく**null**です。`if (!reducedMotion)`のような真偽判定は、**初回レンダーだけ分岐が意図と逆に倒れます**（nullは偽なので「低減設定オフ」と同じ枝に入る）。react-spring自身のアニメーションは同じフックが設定するskipAnimationがペイント前に効くため走りません。問題になるのはそれ以外です。`renderToString`で試すと戻り値はnullのままで、**サーバー側で生成されるHTMLは「低減設定オフ」の枝で書き出されます**。react-spring以外の制御（CSSクラスの付け外しや動画の自動再生など）にこの分岐を使っている箇所も、初回だけ想定と逆に動きます。分岐が必要なら`reducedMotion === true`のように明示的に比較してください。

この副作用を踏まえると、**useReducedMotionはアプリのルート付近で1回だけ呼ぶべきで、個々のアニメーションコンポーネントで呼ぶべきではありません**。コンポーネントごとに動きの有無を切り替えたいだけなら、このフックは使わず、自前でmatchMediaを読んでuseSpringの`immediate`に渡すほうが影響範囲を閉じられます。アクセシビリティ要件で全停止が正しいならルートで呼ぶ、部分制御が要件なら使わない、という判断で切り分けるのが実務的です。

## react-motionとの違い｜2017年で更新が止まった旧世代からの移行

react-springを調べていると、名前の似たreact-motionが並んで出てきます。結論から言えば、新規実装でreact-motionを選ぶ理由はありません。npm registryを見ると最新版は0.5.2で、公開日は2017年10月3日です。以後9年近くリリースがなく、React 18の並行レンダリングやReact 19を前提にした動作保証はありません。対してreact-springは10.1.2が2026年6月に出ており、peerDependenciesでReact 19を明示しています。

移行そのものは大掛かりになりません。どちらもイージング曲線と再生時間ではなく、バネのパラメータで動きを決める思想が共通しているためです。react-motionは`<Motion>`の子として関数を渡し補間済みスタイルを受け取る書き方でしたが、この部分はuseSpringがそのまま担います。スタイル生成のブロックをフック呼び出しへ置き換え、描画側を`animated.div`に変えるだけです。剛性と減衰を指定していた`spring(value, { stiffness, damping })`は`config: { tension, friction }`に対応します。しかも数値の読み替えすら要りません。react-motion 0.5.2のpresets.jsは`noWobble { stiffness: 170, damping: 26 }`・`gentle { 120, 14 }`・`wobbly { 180, 12 }`・`stiff { 210, 20 }`で、**react-springのdefault・gentle・wobbly・stiffと4種すべて実数値が一致します**（react-springがreact-motionのプリセットを引き継いでいるため）。noWobbleをdefaultと読み替えるだけで、動きの質感はそのまま移せます。

ただし、既存プロジェクトでreact-motionが問題なく動いていて、React 17以前で止める方針が確定しているなら置き換える必要はありません。移行が必要になるのはReactのメジャーバージョンを上げるときです。

## よくある質問

### react-springと@react-spring/webのどちらをインストールすべきですか？

ブラウザ向けなら`@react-spring/web`です。アンブレラの`react-spring`はkonva・three・zdog・nativeを含む6パッケージに依存するため、Webしか使わない構成では不要な依存が増えます。加えてv11でアンブレラ自体が削除される予定で、npmにもアンブレラの11系は公開されていません（11.0.0-beta.0が出ているのは`@react-spring/web`側です）。

### useSpringの戻り値が配列になるときとオブジェクトになるときがあるのはなぜですか？

仕様上のオーバーロードです。第1引数に関数を渡した場合、または第2引数に依存配列を渡した場合は`[SpringValues, SpringRef]`のタプルが返り、設定オブジェクトだけを渡した場合はSpringValuesが単体で返ります。サンプルをコピーして`styles.opacity`がundefinedになるときは、タプルで返っているのにオブジェクトとして受けている（またはその逆）のが原因です。

### useSpringsで特定の要素だけアニメーションを更新できますか？

できます。useSpringsが返すAPIのstartには関数を渡せて、引数にインデックスが渡るためです。`api.start(i => ({ opacity: i % 2 === 0 ? 1 : 0 }))`のように書くと、条件に合う要素だけが新しい目標値へ動き、それ以外は現在値のまま保持されます。3要素で試した結果は順に1.00、0.00、1.00で、対象外の要素は動きません。

### React 19でreact-springは動きますか？

v10以上であれば動きます。React 19対応はv10.0.0（2025年5月15日公開）で入りました。v9系の最終版9.7.5のpeerDependenciesはReact 18までで、React 19は範囲外です。移行後にpeer依存の警告やインストール失敗が出るなら、react-springがv9のまま残っていないかを先に確認してください。

### react-motionからreact-springへ移行する必要はありますか？

Reactのバージョンを上げる予定があるなら必要です。react-motionは2017年10月3日公開の0.5.2で更新が止まっています。`<Motion>`のスタイル生成部分をuseSpringへ置き換える形で段階的に移せて、プリセットの数値もそのまま使えます（対応関係は本文のreact-motionとの違いの章）。

## 関連記事

- [react-rndでドラッグ＆リサイズ｜React 19対応と実装方法](/tech/details/3532/)
- [React Contextの使い方｜createContext・useContextの実装とReact 19の新記法](/tech/details/2681/)
- [React Compilerとは？自動メモ化の仕組みと導入方法・useMemoとの違いを解説](/tech/details/9415/)
- [Framer Motion（現Motion／motion/react）とは｜使い方・motionコンポーネント・イージング(easing)の基本](/tech/details/3016/)

---

出典: [react-springの使い方｜useSpring・useTrail・useChainをv10系で実装する](<https://www.issoh.co.jp/tech/details/4256/>)（株式会社一創）
