mcpField
U
unistyles-v2-to-v3-migration

unistyles-v2-to-v3-migration

github.com/jpudysz開発3.0k75 回インストール今日更新
リポジトリ

unistyles-v2-to-v3-migration について

React Native プロジェクトを react-native-unistyles v2 から v3 へ移行するためのスキルです。ビルド設定、API、ランタイム、テストの変更を順序立てて案内します。

主な機能

  1. 01v2 から v3 への移行手順
  2. 02Babel と Metro の設定案内
  3. 03API 置換とランタイム変更
  4. 04Expo Router の統合手順
  5. 05TypeScript とテストの設定

活用シーン

  1. 01Unistyles v3 アップグレードの計画
  2. 02スタイルとテーマ参照の更新
  3. 03Expo Router アプリの調整
  4. 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.md
migration-patterns.md
v3-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)、enabledPlugins
  • statusBar.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 への移行

v2v3
createStyleSheetStyleSheet.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.hairlineWidthStyleSheet.hairlineWidth
statusBar.setColor()削除済み
navigationBar.setColor()削除済み
import { StyleSheet } from 'react-native'import { StyleSheet } from 'react-native-unistyles'(完全なポリフィル)

サードパーティコンポーネントの判断フロー

サードパーティコンポーネントでテーマ値や Unistyles のスタイルが必要な場合:

  1. style プロパティを持つ react-native/react-native-reanimated のコンポーネントですか? -> Unistyles のスタイルを直接渡せばよく、ほかに必要な作業はありません
  2. contentContainerStyle を持つ react-native のコンポーネントですか? -> withUnistyles でラップします(style と contentContainerStyle を自動でマッピングします)
  3. 内部で react-native のコンポーネントを使用するサードパーティコンポーネントですか? -> Babel プラグインの autoProcessPaths(標準以外のインポートの場合は autoRemapImports)で許可リストに追加します
  4. テーマから導出されるスタイル以外のプロパティ(例:color)が必要ですか?または手順 3 が失敗しましたか? -> withUnistyles でラップします
  5. 上記のどれも機能しませんか? -> 代替手段として useUnistyles() フックを使用します

重要なルール

  1. 新アーキテクチャは必須です - RN 0.76 以降ではデフォルトで有効になっており、RN 0.82 以降では唯一のアーキテクチャです。RN 0.81 では、オプトアウトしていないことを確認してください
  2. スタイルをスプレッド構文で展開しないでください - 常に配列構文 [styles.a, styles.b] を使用してください
  3. ビルド時の統合が必須です - Babel プラグイン(デフォルト)、実験的な Metro 統合(3.6.0 以降。Babel プラグインとの併用は不可)、または Re.Pack 用の RepackUnistylePlugin のいずれかが必要です。いずれも使用しない場合、スタイルはリアクティブになりません。babel.config.js の plugins に babel-plugin-react-compiler を記載しているベア RN アプリでは、Babel プラグインを引き続き使用してください
  4. StyleSheet は react-native-unistyles からのみインポートしてください - すべての RN StyleSheet API(hairlineWidth、compose、flatten、absoluteFill など)をポリフィルするため、import { StyleSheet } from 'react-native' は削除してください
  5. StyleSheet は react-native-unistyles から直接インポートすることを推奨します - 独自のパッケージやエイリアス(例:@myorg/design-system)から再エクスポートする場合、それを使用する root 外のファイルは autoProcessImports に記載する必要があります
  6. コンポーネントのレンダー中にスタイルへアクセスする前に、styles.useVariants({...}) を呼び出す必要があります(必ずオブジェクトを渡してください)
  7. React 19 以降が必須です - 古い React バージョンでは、Unistyles はインポート時にエラーをスローします
  8. initialTheme と adaptiveThemes: true を併用しないでください - StyleSheet.configure がエラーをスローします

リファレンスファイル

コメント