unistyles-v2-to-v3-migration について
React Native プロジェクトを react-native-unistyles v2 から v3 へ移行するためのスキルです。ビルド設定、API、ランタイム、テストの変更を順序立てて案内します。
主な機能
- 01v2 から v3 への移行手順
- 02Babel と Metro の設定案内
- 03API 置換とランタイム変更
- 04Expo Router の統合手順
- 05TypeScript とテストの設定
活用シーン
- 01Unistyles v3 アップグレードの計画
- 02スタイルとテーマ参照の更新
- 03Expo Router アプリの調整
- 04移行後のテスト設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jpudysz/ react-native-unistyles --skill unistyles-v2-to-v3-migrationインストール後、Claude Code で/unistyles-v2-to-v3-migrationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/common-pitfalls.mdmigration-patterns.mdv3-api-reference.md詳細
Unistyles v2 から v3 への移行スキル
React Native のコードベースを react-native-unistyles v2 から v3 に移行します。このワークフローに正確に従ってください。v3 は C++ コア(Nitro Modules)を備えた完全な書き直し版で、再レンダリングは発生せず、ビルド時に StyleSheet を処理する Babel プラグインを使用します。
前提条件
- React Native 0.81.0 以降と New Architecture が必須(RN 0.76 以降はデフォルトで有効、RN 0.82 以降は唯一のアーキテクチャ)
- React 19 以降(Unistyles が実行時に必須とします)
react-native-nitro-modules(ネイティブブリッジの依存関係。Unistyles 3.4.0 以降では 0.37.1 以降が必要)react-native-edge-to-edgeは不要(v3.1.0 以降はオプションで、現在は非推奨)です。代わりにandroid/gradle.propertiesでedgeToEdgeEnabled=trueを設定します(Expo SDK 54 以降では自動的に有効になります)- Expo SDK 54 以降(Expo を使用する場合。Expo Go とは互換性がなく、dev client または prebuild が必要)
- Xcode 16.4 以降(iOS、Nitro Modules の要件)、iOS 15.1 以降
移行ワークフロー
以下の手順を順番どおりに実行してください。次の手順に進む前に、各手順を完了する必要があります。
ステップ 1:v3 をインストールして Babel プラグインを設定する
v3 と Nitro Modules を一緒にインストールし、ネイティブアプリを再ビルドします(pods/Gradle)。
yarn add react-native-unistyles@3 [email protected] # or npm / bun / pnpm
Babel プラグインを追加します。
// babel.config.js
module.exports = {
plugins: [
['react-native-unistyles/plugin', { root: 'src' }] // your app source root
]
}
root オプションは必須です(指定しないとプラグインがエラーをスローし、プロジェクトルート自体を指定することもできません)。root 配下のすべてのファイルが、インポート内容にかかわらず処理されます。root の外にあるファイルは、react-native-unistyles をインポートしている場合、autoProcessImports に指定されたインポートを含む場合、または autoProcessPaths に一致する場合にのみ処理されます(autoProcessPaths には、常に 2 つの react-native-reanimated コンポーネントのパスが含まれ、独自のパスはそのパスに追加されます)。node_modules は、autoProcessPaths/autoRemapImports で許可リストに追加しない限り無視されます。
React Compiler を使用する場合、Unistyles プラグインは plugins 配列で React Compiler より前に指定する必要があります。
3.6.0 以降、Metro アプリでは、代わりに実験的なゼロ設定の Metro 統合を使用できます。react-native-unistyles/metro の withUnistylesConfig を使って module.exports = withUnistylesConfig(getDefaultConfig(__dirname)) と設定し、babel.config.js には Unistyles のエントリーを追加しません(両方を設定するとエラーになります)。root/autoProcess* オプションは不要で、node_modules も対象になります。babel.config.js の plugins に babel-plugin-react-compiler を記載するベア React Native アプリでは、Babel プラグインを引き続き使用してください。Babel プラグインがデフォルトです。
アプリを Re.Pack でバンドルする場合は、babel.config.js にプラグインを追加しないでください。代わりに、react-native-unistyles/repack-plugin の RepackUnistylePlugin を unistylesPluginOptions: { root: 'src' } とともに使用します。
ステップ 2:UnistylesRegistry を StyleSheet.configure に置き換える
- import { UnistylesRegistry } from 'react-native-unistyles'
+ import { StyleSheet } from 'react-native-unistyles'
- UnistylesRegistry
- .addThemes({ light: lightTheme, dark: darkTheme })
- .addBreakpoints({ sm: 0, md: 768, lg: 1200 })
- .addConfig({
- adaptiveThemes: true,
- initialTheme: 'dark',
- plugins: [myPlugin],
- experimentalCSSMediaQueries: true,
- windowResizeDebounceTimeMs: 100,
- disableAnimatedInsets: true
- })
+ StyleSheet.configure({
+ themes: { light: lightTheme, dark: darkTheme },
+ breakpoints: { sm: 0, md: 768, lg: 1200 },
+ settings: {
+ adaptiveThemes: true // OR initialTheme: 'dark' - never both
+ }
+ })
削除された設定: plugins、experimentalCSSMediaQueries(常に有効になりました)、windowResizeDebounceTimeMs(デバウンスなし)、disableAnimatedInsets(インセットによる再レンダリングは発生しなくなりました)。
v3 の settings が受け付けるキー: adaptiveThemes、initialTheme、CSSVars(Web)、nativeBreakpointsMode(デフォルトは 'pixels'、または 'points')。それ以外のキーを指定するとエラーになります。initialTheme と adaptiveThemes: true は同時に指定するとエラーになります(相互排他)。adaptiveThemes には light と dark の両方のテーマが必要で、最初のブレークポイントは 0 である必要があります。StyleSheet.create が評価される前に StyleSheet.configure を呼び出してください(エントリーポイントで、設定ファイルを最初にインポートします)。
ステップ 3:すべての StyleSheet インポートと createStyleSheet を置き換える
Unistyles の StyleSheet は React Native の StyleSheet の完全なポリフィルであり、hairlineWidth、compose、flatten、absoluteFill、absoluteFillObject を含みます。インポート元を 1 つに統一するため、import { StyleSheet } from 'react-native' をすべて import { StyleSheet } from 'react-native-unistyles' に置き換えてください。
- import { StyleSheet } from 'react-native'
- import { createStyleSheet } from 'react-native-unistyles'
+ import { StyleSheet } from 'react-native-unistyles'
- const stylesheet = createStyleSheet(theme => ({
+ const styles = StyleSheet.create(theme => ({
container: {
backgroundColor: theme.colors.background
}
}))
ステップ 4:すべての useStyles フックを削除する
- import { useStyles } from 'react-native-unistyles'
const MyComponent = () => {
- const { styles, theme } = useStyles(stylesheet)
return <View style={styles.container} />
}
StyleSheet.create で作成したスタイルは直接使用するため、フックは不要です。Babel プラグインがビルド時にリアクティビティを処理します。
動的関数(styles.box(width, color))は引き続き使用できますが、引数はシリアライズ可能(文字列、数値、ブール値、null、undefined、プレーンな配列/オブジェクト)である必要があります。Unistyles は引数を C++ に保存し、テーマやランタイムの変更時に関数を再実行するためです。関数は削除され、Date/Map/Set/クラスインスタンスはプレーンなオブジェクトになります。代わりに、事前に計算した値を渡してください。
ステップ 5:useInitialTheme を settings.initialTheme に置き換える
- import { useInitialTheme } from 'react-native-unistyles'
-
- const App = () => {
- useInitialTheme(storage.getString('preferredTheme') ?? 'light')
- return <Stack />
- }
+ // In your configure call:
+ StyleSheet.configure({
+ settings: {
+ initialTheme: () => storage.getString('preferredTheme') ?? 'light'
+ }
+ })
initialTheme には文字列または同期関数を指定できます。
ステップ 6:テーマへのアクセスに useStyles() を置き換える
スタイルシートを使わず、テーマ/ランタイムを取得するためだけに useStyles() を使用していたコンポーネントの場合:
オプション A - withUnistyles(テーマ由来のプロパティを渡す場合に推奨):
import { withUnistyles } from 'react-native-unistyles'
const UniButton = withUnistyles(Button, (theme, rt) => ({
color: theme.colors.primary,
size: rt.screen.width > 400 ? 'large' : 'small'
}))
// Usage: <UniButton />
オプション B - useUnistyles フック(手軽な移行方法):
import { useUnistyles } from 'react-native-unistyles'
const MyComponent = () => {
const { theme, rt } = useUnistyles()
return <Text style={{ color: theme.colors.primary }}>{rt.screen.width}</Text>
}
警告: useUnistyles は、読み取った theme/rt の値が変更されるたびにコンポーネント全体を再レンダリングします(購読は分割代入時ではなく、プロパティへのアクセス時に作成されます)。パフォーマンスのため、withUnistyles または StyleSheet.create(theme => ...) を優先してください。iOS/Android では、テーマ変更時に withUnistyles コンポーネントは常に再レンダリングされます。また、withUnistyles のマッピングにはミニランタイムが渡され、style/contentContainerStyle は返せません(これらは自動的にマッピングされます)。再マウントを強制するために key を返すことはできます。
ステップ 7:バリアント選択を更新する
- const { styles } = useStyles(stylesheet, { size: 'large', color: 'primary' })
+ styles.useVariants({ size: 'large', color: 'primary' })
コンポーネントの先頭で(フックと同様に)styles.useVariants() を呼び出してください。バリアントを使用するスタイルにアクセスする前に呼び出す必要があります。常にオブジェクトを指定します。default バリアントを選択するには {} を渡してください。styles.useVariants(undefined) は iOS/Android で例外をスローします。useVariants を呼び出さない場合、バリアント(default を含む)は無視されます。
ステップ 8:スタイルのスプレッドを修正する(重要)
v3 のスタイルは C++ のプロキシオブジェクトです。スプレッドするとバインディングが壊れます。
- <View style={{ ...styles.container, ...styles.extra }} />
+ <View style={[styles.container, styles.extra]} />
- <View style={{ ...styles.container, marginTop: 10 }} />
+ <View style={[styles.container, { marginTop: 10 }]} />
{...styles.x} は絶対に使用しないでください。必ず [styles.x, styles.y] の配列構文を使用してください。
ステップ 9:プラグインを削除する(代わりにテーマ内で静的関数を使用する)
プラグインシステムは削除されました。プラグインを、テーマまたは StyleSheet 内の静的関数に置き換えてください。
- // Plugin approach (v2)
- const fontPlugin: UnistylesPlugin = {
- name: 'fontPlugin',
- onParsedStyle: (_key, styles) => {
- if ('fontWeight' in styles) {
- styles.fontFamily = styles.fontWeight === 'bold' ? 'Roboto-Bold' : 'Roboto-Regular'
- }
- return styles
- }
- }
+ // v3: Use a helper function in your theme or directly
+ const styles = StyleSheet.create(theme => ({
+ text: {
+ fontFamily: theme.utils.getFontFamily('bold')
+ }
+ }))
ステップ 10:UnistylesProvider を削除する
UnistylesProvider はなくなりました。コンポーネントツリーから削除するだけで構いません。
ステップ 11:UnistylesRuntime の使用方法を更新する
名前が変更された/変更されたメソッド(その他については TypeScript の型に従ってください):
UnistylesRuntime.setRootViewBackgroundColor(color?)- アルファパラメーターはなくなりました。React Native の任意のカラーを受け付けます。透明にリセットするには省略してくださいUnistylesRuntime.hairlineWidthの代わりにStyleSheet.hairlineWidth
削除されたメソッド:
addPlugin(plugin)、removePlugin(plugin)、enabledPluginsstatusBar.setColor(color)、navigationBar.setColor(color)(Android 15 で非推奨)
UnistylesRuntime の v3 システムバー/インセット API:
statusBar.setHidden(hidden, animation?)-animationは'none' | 'fade' | 'slide'です(iOS で使用)statusBar.setStyle(style, animated?)-styleはStatusBarStyle列挙型です(react-native-unistylesからエクスポートされるStatusBarStyle.Default | Light | Dark)navigationBar.setHidden(hidden)- Android のみ(iOS ではナビゲーションバーの寸法は常に0です)setImmersiveMode(enabled)- Android ではステータスバーとナビゲーションバーを非表示にし、iOS ではステータスバーのみを非表示にします- キーボードのインセットには
insets.imeを使用します
システムバーには UnistylesRuntime.statusBar/UnistylesRuntime.navigationBar を通してのみアクセスしてください。ステータスバー/ナビゲーションバーの独立したエクスポートはありません。
ステップ 12:TypeScript 宣言を更新する
- type AppThemes = { light: typeof lightTheme, dark: typeof darkTheme }
+ type AppThemes = typeof themes // where themes = { light: lightTheme, dark: darkTheme }
+ type AppBreakpoints = typeof breakpoints
declare module 'react-native-unistyles' {
export interface UnistylesThemes extends AppThemes {}
+ export interface UnistylesBreakpoints extends AppBreakpoints {}
}
interface X extends typeof y は有効な TypeScript ではありません。必ず型エイリアスを経由してください。便利な v3 のエクスポート:列挙型 ColorScheme、Orientation、StatusBarStyle、IOSContentSizeCategory、AndroidContentSizeCategory、WebContentSizeCategory、および型 UnistylesVariants、UnistylesMiniRuntime。
Step 13: キーボード/IME の処理を更新する
- import { useAnimatedKeyboard } from 'react-native-reanimated'
- const keyboard = useAnimatedKeyboard()
+ // Use ime inset in StyleSheet
+ const styles = StyleSheet.create((theme, rt) => ({
+ container: {
+ paddingBottom: rt.insets.ime
+ }
+ }))
Step 14: テスト用のモックを設定する
// package.json (or jest.config.js)
{
"jest": {
"setupFiles": [
"react-native-unistyles/mocks",
"./unistyles.ts"
]
}
}
設定ファイル(StyleSheet.configure の呼び出し)は、モックの後に配置する必要があります。Babel プラグインは NODE_ENV=test の場合(Jest のデフォルト)に何もしません。モックは常に最初に登録されたテーマを使用し、ランタイムの寸法/インセットには 0 を返し、バリアントを取り除き、Display/Hide/ScopedTheme は何もレンダリングしません。解決済みのスタイルをアサートしないでください。
Expo Router との統合
プロジェクトで Expo Router を使用している場合、Unistyles の初期化前に Expo Router がルートを解決するため、追加の手順が必要です。
1. package.json のメインエントリーポイントを変更します。
{ "main": "index.ts" }
2. ルーターより前に Unistyles の設定をインポートする index.ts を作成します。
import 'expo-router/entry'
import './unistyles' // your StyleSheet.configure() file
3. 静的レンダリングの場合(Expo SDK 52 以降): +html.tsx にも Unistyles の設定をインポートします。
import '../unistyles' // ensures Unistyles initializes for each static page
import { ScrollViewStyleReset } from 'expo-router/html';
import { type PropsWithChildren } from 'react';
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: PropsWithChildren) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
詳しくは https://www.unistyl.es/v3/guides/expo-router をご覧ください。
クイックリファレンス:v2 API から v3 への移行
| v2 | v3 |
|---|---|
createStyleSheet | StyleSheet.create |
useStyles(stylesheet) | styles を直接使用(フックは不要) |
useStyles() でテーマを取得 | useUnistyles() または withUnistyles |
useStyles(ss, variants) | styles.useVariants(variants) |
useInitialTheme(name) | configure の settings.initialTheme |
UnistylesRegistry.addThemes() | StyleSheet.configure({ themes }) |
UnistylesRegistry.addBreakpoints() | StyleSheet.configure({ breakpoints }) |
UnistylesRegistry.addConfig() | StyleSheet.configure({ settings }) |
UnistylesProvider | 削除済み(不要) |
UnistylesPlugin | 削除済み(テーマ関数を使用) |
{...styles.x, ...styles.y} | [styles.x, styles.y] |
UnistylesRuntime.hairlineWidth | StyleSheet.hairlineWidth |
statusBar.setColor() | 削除済み |
navigationBar.setColor() | 削除済み |
import { StyleSheet } from 'react-native' | import { StyleSheet } from 'react-native-unistyles'(完全なポリフィル) |
サードパーティコンポーネントの判断フロー
サードパーティコンポーネントでテーマ値や Unistyles のスタイルが必要な場合:
styleプロパティを持つreact-native/react-native-reanimatedのコンポーネントですか? -> Unistyles のスタイルを直接渡せばよく、ほかに必要な作業はありませんcontentContainerStyleを持つreact-nativeのコンポーネントですか? ->withUnistylesでラップします(styleとcontentContainerStyleを自動でマッピングします)- 内部で
react-nativeのコンポーネントを使用するサードパーティコンポーネントですか? -> Babel プラグインのautoProcessPaths(標準以外のインポートの場合はautoRemapImports)で許可リストに追加します - テーマから導出されるスタイル以外のプロパティ(例:
color)が必要ですか?または手順 3 が失敗しましたか? ->withUnistylesでラップします - 上記のどれも機能しませんか? -> 代替手段として
useUnistyles()フックを使用します
重要なルール
- 新アーキテクチャは必須です - RN 0.76 以降ではデフォルトで有効になっており、RN 0.82 以降では唯一のアーキテクチャです。RN 0.81 では、オプトアウトしていないことを確認してください
- スタイルをスプレッド構文で展開しないでください - 常に配列構文
[styles.a, styles.b]を使用してください - ビルド時の統合が必須です - Babel プラグイン(デフォルト)、実験的な Metro 統合(3.6.0 以降。Babel プラグインとの併用は不可)、または Re.Pack 用の
RepackUnistylePluginのいずれかが必要です。いずれも使用しない場合、スタイルはリアクティブになりません。babel.config.jsのpluginsにbabel-plugin-react-compilerを記載しているベア RN アプリでは、Babel プラグインを引き続き使用してください StyleSheetはreact-native-unistylesからのみインポートしてください - すべての RN StyleSheet API(hairlineWidth、compose、flatten、absoluteFillなど)をポリフィルするため、import { StyleSheet } from 'react-native'は削除してくださいStyleSheetはreact-native-unistylesから直接インポートすることを推奨します - 独自のパッケージやエイリアス(例:@myorg/design-system)から再エクスポートする場合、それを使用するroot外のファイルはautoProcessImportsに記載する必要があります- コンポーネントのレンダー中にスタイルへアクセスする前に、
styles.useVariants({...})を呼び出す必要があります(必ずオブジェクトを渡してください) - React 19 以降が必須です - 古い React バージョンでは、Unistyles はインポート時にエラーをスローします
initialThemeとadaptiveThemes: trueを併用しないでください -StyleSheet.configureがエラーをスローします
リファレンスファイル
- 網羅的な変更前/変更後のコード例については、references/migration-patterns.md を参照してください
- すべてのオーバーロードとオプションを含む v3 API の全容については、references/v3-api-reference.md を参照してください
- エラーのトラブルシューティングと既知の問題については、references/common-pitfalls.md を参照してください