Expo SDK 46アップグレード
Expo SDK 46の主な変更
React Native 0.69.5
React Nativeのバージョンが0.69.5にアップグレードされました。React Nativeの0.69では、大きく以下の変更が実施されています。
React 18
React Native 0.69では、デフォルトでReact 18が有効化されています。React 18はSuspenseのサポートやAutomatic Batching、Transitionsなど多くの機能がリリースされています。それらの多くは、Concurrent Renderingという基盤に支えられています。
React Nativeでは、New Architectureを有効化することでConcurrent Renderingを使用できます。
また、React 18では型定義の変更があります。特に影響が大きいのは、React.FunctionComponentやReact.Componentに実装されていたchildrenが削除されたことでしょう。
コンポーネントに定義されているPropsにchildrenを追加するか、PropsをReact.PropsWithChildrenでラップするなどの対応が必要です。
interface MyButtonProps {
color: string;
children?: React.ReactNode;
}
or
interface MyButtonProps {
color: string;
}
const MyButtonComponent: React.FC<React.PropsWithChildren<MyButtonProps>> = (props) => {}
Expo CLI
Expo CLIが一新されました。Githubのリポジトリも、以前はexpo/expo-cliで管理されていましたが、expo/expoに移行されています。
新しいExpo CLIでは、PNPMのサポートや--fixオプションによる依存ライブラリのバージョン修正機能が追加されています。
また、グローバル領域にExpo CLIをインストールする必要がなくなりました。Expoがpackage.jsonのdependenciesに含まれている場合は、node_modules配下のExpo CLI(以降Local Expo CLIと呼びます)が使用されます。
- プロジェクトディレクトリで
npx expo installを実行すると、Local Expo CLIが使用されます - プロジェクトディレクトリで
expo installを実行すると、グローバル領域にインストールされたExpo CLIが使用されます package.jsonのscriptsに{"scripts": {"start": "expo start"}}と定義されている場合は、プロジェクトディレクトリでnpm startを実行するとLocal Expo CLIが使用されます
ただし、新しいExpo CLIにはexpo upgradeやexpo doctorが実装されていません。そのため、Expo SDKのアップグレード作業などは、旧Expo CLIを使用する必要があります。
新しいExpo CLIがインストールされた環境で旧Expo CLIを使用する場合は、expo-cli upgradeのようにexpo-cliコマンドを使用します。
旧Expo CLIからのマイグレーションに関する詳細は、Expo CLI Migrationを参照してください。
Expoがサポートするライブラリ
ライブラリの追加
以下のライブラリが新たにサポート対象となりました。
- React Native Skia
- 2DグラフィックエンジンのSkiaをReact Nativeから使用可能にするライブラリ
- FlashList
FlatListにおけるFPSやメモリ使用に関するパフォーマンスの課題を解決してくれるライブラリ
ライブラリの削除
Expo SDK 45で非推奨となっていたライブラリが削除されました。
マイグレーション方法については、上記ライブラリのリンク先を参照してください。
非推奨となるライブラリ
expo-error-recoveryは非推奨となります。Classic Buildでは必要でしたが、EAS Buildでは不要になったのが理由のようです。
Expo SDK 47では、expo-error-recoveryが削除される予定です。
iOSの最小サポート対象バージョンが12.4に変更
React Native 0.69からは、iOSのサポート対象バージョンが12.4以降になります。
なお、iOS 16はExpo SDK 47でサポート対象になる予定です。iOS 16がサポートされると同時に、iOS 12のサポートは対象外になる見込みです。
Expo SDK 43のサポート終了
Expo SDK 43がサポート対象外になりました。Expo SDK 43を使用している場合は、Expo SDK 44以降にアップグレードする必要があります。
vscode-expoの機能追加
vscode-expoで、全てのExpo configsがサポートされるようになりました。
app.jsonやapp.config.jsonと同様に、以下のConfigファイルもAutoCompleteやValidationの機能が提供されます。
eas.json(EAS Build and Submit)store.config.json(EAS Metadata)expo-module.config.json(Expo modules)
Classic BuildからEAS Buildへのマイグレーション
Expo SDK 46が、Classic Buildをサポートする最後のバージョンとなります。expo build:androidやexpo build:iosを使用している場合は、次回のExpoアップグレードまでにeas buildへ移行する必要があります。
マイグレーションの詳細は、Migrating from "expo build"を参照してください。
Classic UpdateからEAS Updateへのマイグレーション
EAS Updateが正式にリリースされています。Classic Updateより速く、柔軟なアップデートシステムを提供しているようです。
マイグレーションの詳細は、Migrate from Classic Updatesを参照してください。
このアプリで実施したアップグレードの手順
このアプリでは、以下の作業を上から順に実施してExpo SDK 46にアップグレードしました。
- Xcode 14にアップグレード
npm i -g expo-cliを実行して、Expo CLI(旧Expo CLI)のバージョンを最新化expo-cli upgradeを実行して、Expo SDKをアップグレード- 依存ライブラリのバージョン不整合を解消
node_modules、package-lock.jsonを削除して、npm iを実行- expo-template-bare-minimumの更新履歴を確認
- React Native Upgrade Helperを参照して、React Nativeの更新を確認
- このアプリで必要な対応は、expo-template-bare-minimumの更新履歴を確認で対応した内容に含まれていました
- Expoの更新履歴を確認
- このアプリで必要な対応はありませんでした
- React Nativeの更新履歴を確認
- このアプリで必要な対応はありませんでした
- React 18の対応
- 自動テストの修正
- ESLintのエラー・警告対応
- TSCのエラー対応
- React Native Firebase 15の対応
- 既存のパッチファイルの更新
Pods、Podfile.lockを削除して、npm run pod-installを実行- 開発・CIに使用しているツールのバージョンを更新
アップグレードを実施したPull Requestはこちらです。
依存ライブラリのバージョン不整合を解消
expo-cli upgradeを実行すると、Expo SDKとExpoがサポートしているライブラリのバージョンが更新されます。その際に、いくつかの依存ライブラリでバージョンの不整合が発生しました。ここではそれらを解消した手順を記載します。
@react-native-picker/picker
Expo SDK 46がサポートしている@react-native-picker/pickerの2.4.2は、peerDependenciesにReact 18が含まれていません。
そのため、React 18をサポートする2.4.3にアップグレードしました。(fix: Change React peerDependencies version)
react-native-svg
Expo SDK 46がサポートしているreact-native-svgの12.3.0は、React 18で変更された型定義に対応していませんでした。そのため、Expoのissueのコメントに記載されている12.4.4にアップグレードしました。
@react-native-firebase
expo-cli upgradeを実行すると、@react-native-firebase/appが15.4.0にアップグレードされました。それに合わせて、このアプリで使用しているFirebase関連のライブラリも15.4.0にアップグレードしました。
@react-native-firebase/crashlytics@react-native-firebase/messaging
@testing-library/react-hooks
npm uninstall @testing-library/react-hooksを実行して、@testing-library/react-hooksをアンインストールしました。
@testing-library/react-hooksはReact 18をサポートしていません。代わりに、@testing-library/react-nativeを使用します。
詳細は、react-hooks-testing-library - A Note about React 18 Supportを参照してください。
react-native-safe-area-context
Expo SDK 46がサポートしているreact-native-safe-area-contextのバージョンは4.3.1です。しかし、このアプリで使用しているReact Native Elements 3.4.2は、peerDependenciesにreact-native-safe-area-contextのバージョン4以降が含まれていません。
そのため、react-native-safe-area-contextを3.3.2にダウングレードしました。
react-test-renderer、@types/react-test-renderer
React 18のアップグレードに伴い、react-test-renderer、@types/react-test-rendererも18.0.0にアップグレードしました。
expo-template-bare-minimumの更新履歴を確認
expo-template-bare-minimumの更新履歴を確認して、更新されたファイルをこのアプリに反映しました。
その中で、iOSの場合にReact Native Firebaseの設定と競合する箇所があったので、それを解消した手順を記載します。
該当するexpo-template-bare-minimumのPull Requestはこちらです。
このPull Requestでは、AppDelegate.mmでConcurrent Renderingの有効化を判定する値を初期設定する機能が追加されています。
一方このアプリでは、iOSでアプリがバックグラウンドや停止状態中にPush通知を受信した際の対応として、以下に記載されている対応を追加しています。
どちらも、初期設定をNSDictionaryとして作成してアプリに渡しています。そのため、それらの初期設定(NSDictionary)をマージする必要がありました。
React Native Firebase Cloud Messagingでは、RNFBMessagingModule.mのaddCustomPropsToUserPropsにNSDictionaryを渡すことができます。渡されたNSDictionaryは、React Native Firebase Cloud Messagingが生成する初期設定とマージされます。今回は、それを利用することで初期設定のマージを実施しました。
NSDictionary *initProps = [self prepareInitialProps];
NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:initProps withLaunchOptions:launchOptions];
React 18の対応
Expo SDK 46の主な変更に記載した通り、React 18ではSuspenseのサポートやAutomatic Batching、Transitionsなど多くの機能がリリースされています。しかし、それらはReact NativeのNew Architectureを有効化しないと使用できません。
Expoをはじめとするエコシステムでは、New Architectureを完全にサポートしているライブラリはまだ少ないです。このアプリも、依存しているライブラリがNew Architectureに対応するまでは有効化できないのが現状です。
そのため、React 18の対応としては、React 18アップグレードガイドに記載されているTypeScript 型定義の変更のみを実施しました。型定義の全ての変更は、React 18の型定義に関するPull Requestに記載されています。
このアプリでは、Removal of implicit childrenの変更のみが該当しました。対応するコンポーネントの数が多かったので、React 18アップグレードガイドでも紹介されているtypes-react-codemodを利用して修正しました。
types-react-codemodは、React.FunctionComponentのPropsを、React.PropsWithChildrenでラップするように自動修正してくれます。ただし、注意点として以下がありました。
childrenを必要としていないコンポーネントも、React.PropsWithChildrenでラップしてしまう- コンポーネントのPropsが存在しない場合、
React.PropsWithChildren<unknown>のようにジェネリクスの型にunknownが指定されてしまう
これらは動作的には問題ありませんが、不要な定義を残すとコードの可読性を損なうため、types-react-codemodの実行後に手動で削除しました。
修正後のコード例は以下になります。
interface ComponentBProps {
color: string;
}
interface ComponentCProps {
color: string;
}
// コンポーネントのPropsもchildrenも受け取らない場合
const ComponentA: React.FC = () => {}
// コンポーネントのPropsのみ受け取る場合
const ComponentB: React.FC<ComponentBProps> = (props) => {}
// コンポーネントのPropsもchildrenも受け取る場合
const ComponentC: React.FC<React.PropsWithChildren<ComponentCProps>> = ({children, ...props}) => {}
自動テストの修正
@testing-library/react-hooksはReact 18をサポートしていません。代わりに、@testing-library/react-nativeのactやrenderHookを使用するように修正しました。
ESLintのエラー・警告対応
一部のコンポーネントで、@typescript-eslint/no-throw-literalの警告が発生していました。
unknown型をthrowしていたので、このアプリで用意しているErrorクラスを継承したRuntimeErrorでラップしてthrowするように修正しました。
また、React.VoidFunctionComponent(React.VFC)が非推奨になったことから、eslint-plugin-deprecationのチェックでエラーが発生していました。そのため、React.VoidFunctionComponent(React.VFC)をReact.FunctionComponent(React.FC)に変更しました。
今回は利用しませんでしたが、types-react-codemodには、React 19の対応としてReact.VFCをReact.FCに自動修正してくれる機能も存在します。
詳細は、deprecated-void-function-componentを参照してください。
TSCのエラー対応
一部のTSファイルで、以下のエラーが発生していました。
TS7006: Parameter '_' implicitly has an 'any' type.
使用していない変数の型を省略していたため、型を明示的に指定しました。
React Native Firebase 15の対応
iOSの場合、React Native Firebaseのバージョン15(Firebase iOS SDKのバージョン9)以降では、Frameworkを使用します。Frameworkを使用した場合、React Native Firebaseのドキュメントによると以下の制限があります。
- React NativeのNew Architectureは有効化できない
- Flipperは使用できない
- JavaScriptのエンジンとして
Hermesを使用する場合は、Static Frameworkを使用する必要がある
このアプリでは、use_frameworksを使用してFrameworkを有効化して、Flipperを無効化する対応をしました(New Architectureは以前から無効化しています)。
+ $RNFirebaseAsStaticFramework = true
/* ~省略~ */
- use_flipper!(configurations: ['Debug', 'DebugAdvanced'])
+ # use_flipper!(configurations: ['Debug', 'DebugAdvanced'])
/* ~省略~ */
{
- "expo.jsEngine": "jsc"
+ "expo.jsEngine": "jsc",
+ "ios.useFrameworks": "static"
}
既存のパッチファイルの更新
このアプリでは、patch-packageを使用して、以下のライブラリにパッチファイルを適用してます。パッチ内容の詳細は、こちらを参照してください。
- @react-native-community+cli-platform-ios
- expo-splash-screen
- react-native-reanimated
- react-native-elements
React Native Elements以外は、Expo SDKのアップグレードに伴いバージョンが上がりました。しかし、適用していたパッチファイルはまだ必要な対応だったため、パッチファイルは削除せずに各ライブラリのバージョンに合わせてファイル名をリネームしました。
React Native Elementsのバージョンは変更していないのですが、React 18のアップグレードに伴い、ThemeProviderのPropsからchildrenが削除されていました。そのため、ThemeProviderのPropsにchildren?: React.ReactNodeを定義するパッチを追加しています。
開発・CIに使用しているツールのバージョンを更新
Expo SDK 46のアップグレードとは関係ありませんが、このタイミングで開発に使用しているツールのバージョンを更新しました。
| Tool | from | to |
|---|---|---|
| Node.js | 16.15.1 | 16.18.0 |
| Java | zulu-11.56.19 | zulu-11.58.23 |
CIに使用しているツールのバージョンも更新しました。
| Tool | from | to |
|---|---|---|
| Node.js | 16.15.1 | 16.18.0 |
| Xcode | 13.3.1 | 14.0.1 |