概要
2020年2月にReact Navigationのv5がリリースされました。
React Navigation 5.0 - A new way to navigate | React Navigation
Snapmartアプリでは、React Navigation v4を使用しています。
React Navigation v4では、Stack NavigatorにJavaScriptを使用していましたが、ネイティブのような感覚とパフォーマンスが低下する場合がありました。
React Navigation v5では、Native navigation primitivesを使用するStack Navigatorに変更することで改善がされました。
Stack Navigatorは多くの画面遷移で使用するため、このタイミングでアップグレードすることにしました。
今回は、Snapmartアプリで使用しているReact Navigationをv4からv5にアップグレードした際の対応についてまとめます。
手順
React Navigationはアップグレードガイドが非常に丁寧に書かれています。
Upgrading from 4.x | React Navigation
このリストの中で、実際に使用している処理については対応をしていく必要があります。 対応したことは以下の通りです。
- パッケージ名の変更
- createAppContainerからNavigationContainerに変更
- コンポーネント化されたN Navigatorに変更
- navigation propから分割されたroute propを使用
- navigationOptions を各Screenで設定するように修正
- Navigation eventsの変更
- ディープリンクの記述修正
- CommonActionsの変更
1. パッケージ名の変更
各パッケージ名が変更されたので、記載されている内容にしたがって変更をしていきます。
Upgrading from 4.x | React Navigation
若干ハマった点としては、パッケージ名を変更して起動をするとこのようなエラーが発生しました。
I'm getting "SyntaxError in @react-navigation/xxx/xxx.tsx" or "SyntaxError: /xxx/@react-navigation/xxx/xxx.tsx: Unexpected token"
というエラーが表示されたので、以下のコマンドで対応します。
$ rm -rf node_modules $ rm yarn.lock $ yarn
2. createAppContainerからNavigationContainerに変更
こちらは単純にcreateAppContainerからNavigationContainerに変更します。
Upgrading from 4.x | React Navigation
3. コンポーネント化されたN Navigatorに変更
React Navigation v5の大きな変更として、コンポーネント化があります。
React Navigation v4では以下のような記述でした。
const RootStack = createStackNavigator( { Home: { screen: HomeScreen, navigationOptions: { title: 'My app' }, }, Profile: { screen: ProfileScreen, params: { user: 'me' }, }, }, { initialRouteName: 'Home', defaultNavigationOptions: { gestureEnabled: false, }, } );
React Navigtaion v5からは以下のような書き方になります。
const Stack = createStackNavigator(); function RootStack() { return ( <Stack.Navigator initialRouteName="Home" screenOptions={{ gestureEnabled: false }} > <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'My app' }} /> <Stack.Screen name="Profile" component={ProfileScreen} initialParams={{ user: 'me' }} /> </Stack.Navigator> ); }
変更を自動化できなさそうなので、粛々と修正していきました。
4. navigation propから分割されたroute propを使用
React Navigation v5からは、navigation propからroute propが切り出されました。 影響を受けた点としては、画面遷移時に渡すparameterがroute propから取得しなければならなくなったことです。
- this.props.navigation.state.params.id + this.props.route.params.id
こちらの対応は、sedで一括置換で対応しました。
find src/ -type f -print0 | xargs -0 sed -i -e "s/navigation\.state/route/g"
5. navigationOptions を各Screenで設定するように修正
navigationOptionsもv4では各画面で設定していましたが、v5からはScreenコンポーネントで設定します。
Upgrading from 4.x | React Navigation
こんな感じです。
v4
class ProfileScreen extends React.Component { static navigationOptions = { headerShown: false, }; render() { // ... } }
v5
<Stack.Screen name="Profile" component={ProfileScreen} options={{ headerShown: false }} />
これも自動で変換はできなさそうなので、手で粛々と直します。
6. Navigation eventsの変更
Upgrading from 4.x | React Navigation
v4では、Navigation evenetが4種類ありました。
- willFocus
- didFocus
- willBlur
- didBlur
v5ではシンプルにfocus
とblur
だけになります。これは、v4におけるwillFocus
とwillBlur
です。
また、書き方も変わります。 Class Componentにおいてはこのようになります。
+ componentDidMount() { + const unsubscribe = this.props.navigation.addListener("focus", e => { + // 処理 + }); + } render() { return ( <View style={styles.container}> - <NavigationEvents onWillFocus={payload => { console.log("onWillFocus") }} />
7. ディープリンクの記述修正
Deep linking | React Navigation
ディープリンキングは、pathのハンドリングが変わります。
import { Linking } from 'expo'; import { NavigationContainer, useLinking } from "@react-navigation/native"; const prefix = Linking.makeUrl('/'); function App() { const ref = React.useRef(); const { getInitialState } = useLinking(ref, { prefixes: [prefix], config: { HomeStack: { path: 'stack', screens: { Home: 'home', Profile: 'user', }, }, Settings: 'settings', } }); const [isReady, setIsReady] = React.useState(false); const [initialState, setInitialState] = React.useState(); React.useEffect(() => { getInitialState() .catch(() => {}) .then(state => { if (state !== undefined) { setInitialState(state); } setIsReady(true); }); }, [getInitialState]); if (!isReady) { return null; } return ( <NavigationContainer initialState={initialState} ref={ref}> {/* content */} </NavigationContainer> ); }
ref
を定義し、NavigationContainer
とuseLinking
に渡します。
また、pathの定義は、useLinking
のconfig
で画面を紐づけを行います。
上記の例は、HomeStackとSettingsの2つのタブがあり、 HomeStackタブには、HomeとProfileという2つのスクリーンが存在するイメージです。
この定義だとmyapp://home
でHomeスクリーンにリンクすることができます。
8. CommonActionsの変更
CommonActions reference | React Navigation
最後に、CommonActionにも変更がありました。
reset
を使っていた箇所があったので、修正をします。
+ import { CommonActions } from '@react-navigation/native'; ... - let resetAction = StackActions.reset({ - index: 0, - actions: [NavigationActions.navigate({ routeName: "Account" })], - }); - this.props.navigation.dispatch(resetAction); + this.props.navigation.dispatch( + CommonActions.reset({ + index: 0, + routes: [{ name: "Account" }], + }) + )
まとめ
対応は以上です。
- パッケージ名の変更
- createAppContainerからNavigationContainerに変更
- コンポーネント化されたN Navigatorに変更
- navigation propから分割されたroute propを使用
- navigationOptions を各Screenで設定するように修正
- Navigation eventsの変更
- ディープリンクの記述修正
- CommonActionsの変更
Snapmartは78画面で構成されるアプリですが、手作業の部分が多く、骨が折れました...。
特に、コンポーネント化されたN Navigatorに変更とnavigationOptions を各Screenで設定するように修正する作業は、全ての画面を開いて修正しなけばならなかったのが大変でした。