共有値とスタイル
インラインスタイルの限界をきっかけにuseAnimatedStyleを導入し、useSharedValueとuseAnimatedStyleの引数・戻り値・注意点までひととおり押さえます。
前章では共有値をAnimated.Viewのstyleに直接渡してアニメーションさせました。この書き方は手軽ですが、少し複雑なことをしようとするとすぐに壁にぶつかります。この章ではその壁を確認し、useAnimatedStyleで乗り越えます。あわせて、useSharedValueとuseAnimatedStyleの細かな仕様も整理しておきます。
インラインスタイルの限界
共有値をインラインのスタイルに渡す書き方には、大きな弱点があります。共有値に入っている値を読み取って加工できません。
const width = useSharedValue(100);
<Animated.View style={{ width }} />; // これはできる
<Animated.View style={{ width: width * 5 }} />; // これはできない
widthは共有値そのものであって、ただの数値ではありません。だから* 5のような計算を挟もうとしても期待どおりには動きません。条件分岐やループを絡めた、もう少し凝ったアニメーションを組みたくなると、インラインスタイルだけでは足りなくなります。
useAnimatedStyleでスタイルを組み立てる
ここで登場するのがuseAnimatedStyleです。StyleSheetのスタイルによく似たスタイルオブジェクトを返すフックで、その中では共有値の.valueを読み取って自由に計算できます。返したスタイルをAnimatedコンポーネントのstyleに渡すと、共有値やReactのstateが変わるたびに自動で更新されます。
次のデモは、ボタンを押すたびに四角形が右へ動きます。translateXの値を2倍してからwithSpringに渡している点に注目してください。共有値の.valueにアクセスできるからこそできる書き方です。
「コード」タブを見ると、アニメーションに関わるロジックがuseAnimatedStyleの中に一か所へまとまっているのが分かります。値の加工も、アニメーション関数の適用も、すべてこのフックの中で完結します。これがインラインスタイルにはない利点です。
useSharedValueをくわしく
ここで共有値の仕様を整理しておきます。useSharedValueは初期値を1つ受け取り、共有値を返します。
const sv = useSharedValue(100);
// 読み取り
console.log(sv.value);
// 書き換え
sv.value += 50;
初期値には数値・文字列・真偽値のほか、配列やオブジェクトも渡せます。値の読み書きは.valueプロパティ、またはget/setメソッドで行います。React Compilerを使っている場合は、.valueの直接操作ではなくget/setを使うのが推奨です。
共有値を扱ううえで、いくつか気をつけたい点があります。
- レンダリング中に
.valueを読み書きしないでください。レンダー中の副作用はReactのルール違反です。読み書きはuseAnimatedStyleやuseEffectなど、レンダー中に実行されないコールバックの中で行います sv.valueを書き換えても、通常のReact再レンダーは起きません。共有値はただのJavaScriptオブジェクトだからです- JSスレッドで
.valueを書き換えても、その直後のログには古い値が出ます。UIスレッド側では同期的に、JSスレッド側では非同期に更新が反映されるためです
分割代入は共有値の反応性を壊すので避けてください。
let { value } = sv; // これはダメ
value += 50; // スタイルは更新されない
オブジェクトや配列を入れているときは、プロパティを個別に書き換えるのではなく、まるごと再代入します。大きなデータを部分的に変えたいときは.modifyメソッドが使えます。
const sv = useSharedValue({ x: 0, y: 0 });
sv.value.x = 50; // 反応性が失われる
sv.value = { x: 50, y: 0 }; // これでよい
useAnimatedStyleの細かい挙動
useAnimatedStyleは第1引数にスタイルを返す関数(updater)を取り、第2引数に依存配列を取ります。戻り値はAnimatedコンポーネントのstyleに渡すためのスタイルオブジェクトです。
第2引数の依存配列は、web向けにBabelプラグインを使わない構成のときにだけ関係します。本書のデモはまさにその構成で動いているため、updaterの中で読む共有値やstateを依存配列に明示しています。デモの「コード」タブに依存配列が並んでいるのはこのためです。
そのほかに押さえておきたい挙動をまとめます。
- アニメーションスタイルは静的なスタイルより優先されます。
style={[styles.box, animatedStyle]}のように配列で渡し、動的な部分だけをuseAnimatedStyleで書くと、不要な再計算を避けられます useAnimatedStyleが返す値はAnimatedコンポーネントにしか渡せません。通常のコンポーネントに渡すとエラーになります- updaterの中で共有値を書き換えるのは未定義の動作で、無限ループを招くことがあります
プロパティをアニメーションさせる
widthやtransformのようにstyleに渡す値だけでなく、コンポーネントのpropsをアニメーションさせたい場面もあります。代表例がSVGです。SVGの円は半径などをpropsとして受け取ります。
<Circle cx="50" cy="50" r="10" fill="blue" />
こうしたコンポーネントのpropsをアニメーション可能にするには、createAnimatedComponentでラップし、useAnimatedPropsでpropsを組み立てます。
import Animated, {
useAnimatedProps,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { Svg, Circle } from "react-native-svg";
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
function App() {
const r = useSharedValue(20);
const animatedProps = useAnimatedProps(() => ({
r: withTiming(r.value),
}));
return (
<Svg>
<AnimatedCircle cx="50%" cy="50%" fill="#b58df1" animatedProps={animatedProps} />
</Svg>
);
}
useAnimatedPropsはupdaterがアニメーションさせたいpropsのオブジェクトを返し、その戻り値をコンポーネントのanimatedPropsに渡します。使い方はuseAnimatedStyleとよく似ていて、こちらも.valueにアクセスしてロジックをまとめられます。
このSVGの例はreact-native-svgに依存します。本書のデモ環境にはreact-native-svgを入れていないため、動くデモではなくコード例として示しました。useAnimatedPropsそのものの詳しい使いどころは第5章でもう一度取り上げます。
この章のまとめ
- インラインスタイルは手軽ですが、共有値の値を読み取って加工できません
useAnimatedStyleを使うと.valueにアクセスでき、計算やアニメーション関数をひとまとめにできます- スタイルは
propsではなくstyleに、propsはuseAnimatedPropsで扱います - 共有値の読み書きはレンダー中を避け、分割代入やオブジェクトの部分更新には注意します
次章では、withTimingとwithSpringの設定に踏み込み、動きの表情を自分で調整できるようにします。