はじめに
お久しぶりです!ゆたまです!
もう管理職が板について、スプレッドシート書くか人と話すかしかしてないんじゃないかってぐらいコード書かなくなってきました。とはいえ尋常じゃなく忘れっぽいので管理職むいてないなぁっていつも思いますね。確定申告ですらシーズン来てたのを実家の税理士の叔母に「あんた、ちゃんとだしゃぁよ!」って言われて思い出したぐらいで、誰かに管理されたいっす・・・。誰か僕を管理してください。
だけどコード書くのはやっぱり楽しくまだまだtypescriptとも愛を深め合う時間が足りないのでtypescriptが寂しい思いしてるんじゃないかって不安になってるメンヘラです。
さておき
またかよ!って感じですが、react-navigationV5が出ましたね!
本を書いている関係で触らなきゃいけないんですが、ぶっちゃけいってv2->v3->v4は寝ながらでもmigrateできましたけどv5は結構めんどくさかったです。
本記事執筆段階ではまだやり終えてませんが、久々に記事を書くモチベーションなしでは流石にしんどいので記事を描きながらやっていくことにします。
この記事では主にmigrateをしながらv4との変更点を感じていきつつ、v5の使い方を解説しつつ、時折出てくる僕の今までフラストレーションを感じてもらう感じです。あと僕は基本的に「react-native init」することにこだわっているのでexpo環境ではありませんのでその点も考慮知ていただけたらと思います(依存関係など)。
では、参りまショータイム!
react-navigation
react-navigation
react-nativeにおけるナビゲーションライブラリです!
おそらく、数多あるreact-native関係のルーティングライブラリの中でも安定性(仮)とできることに定評のあるライブラリで多くの開発者を支えながら、その癖と変更点で苦しめてきたライブラリではないのかと思います。
本を書いている関係で、「coming soon!!」って言われた時は、「来んな来んな来んな来んな来んな来んな来んな来んな来んな来んな来んな来んな」って思ってて、見て見ぬ振りを知ていた所、僕の師から到来の報告を受け、関わる事が確定しました。
インストール
パッケージ名がそもそも変わりました。
before | after |
---|---|
react-navigation | @react-navigation/native & @react-navigation/routers |
react-navigation-drawer | @react-navigation/drawer |
react-navigation-stack | @react-navigation/stack |
react-navigation-tabs | @react-navigation/tabs, @react-navigation/bottom-tabsなど |
一旦ココになんとなく書いておきましたが、今まではreact-navigationの中に入っていたものの別になったものや、逆にreact-navigationの中に追加されたものもありますうえすべてを記載知ているわけではないので、とりあえずなんとなくの理解でお願いします。
なので、一旦migrateするときはyarn remove
からのyarn add
をしてください。
また依存関係もちょっと増えているので適宜いりそうなのを追加する必要があります。
僕の場合は、react-native-screen
と@react-native-community/masked-view
が必要でした。
これはどんなルーティングをくんでいるかによりますが、代替みんなおんなじかと思います。
- react-native-reanimated
- react-native-gesture-handler
- react-native-screens
- react-native-safe-area-context
- @react-native-community/masked-view
これから始める人
こんな感じで1Killできます!
yarn add @react-navigation/native
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
iOS
cd ios
pod install
cd ..
余談:hooksについて
いままではreact-navigation-hooks
を使われていた人も多いかと思われますがv5からは不要です。
yarn remove
しましょう。
代わりに@react-navigation/native
からimportしてこれます。
なんか、流行ると「チャラついて、恥ずかしくないのか」みたいなおっさん思考になってきて未だにhooksみると便利なのはわかってはいるし主流なのも理解しているけども古き良き(良いのか?)が廃れていく時流を見てみぬふりできなくなってきましたね・・・。
こんな老害無視してみなさんはhooksを使っていきましょう。
CreateNavigatorの置き換え
非常に残念ながら、CreateXXXNavigator系のAPIが大きく変更になりました。V3 -> V4のときもだったけど何回目だよっていう。
例えば、懐かしいv4のコードを振り返ってみましょう。今から始める人はこんなもの知らなくていいので、issueなどで古いコードを読まないとってならない限りはV4のところすっ飛ばしてください。覚えることは少ないほうが正です。
tabだろうがdrawerだろうが基本的にはいっしょなのでここではStackのコードだけ描きますね!。
V4
import { createStackNavigator } from 'react-navigation-stack'
import { PageA, PageB } from './pages'
import { headerStyle } from './Header'
const cardStyle = {
backgroundColor: '#008080'
}
const StackNavigator = createStackNavigator({
PAGE_A: {
screen: PageA,
navigationOptions: {
headerStyle,
title: 'PageA',
},
},
PAGE_B: {
screen: PageB,
navigationOptions: {
title: 'PageB',
},
}
}, {
cardStyle,
})
こんな感じでしたね。
インターフェースは、
createStackNavigator(routes, defaultConfig)
といった感じで、routesに画面パスや画面名を、defaultConfigに共通の設定を入れることができるようになりました。
V5
ところがどっこいV5になってからは引数は廃止されています。
○ッキンって感じですが、見ていきましょう。
import React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import { PageA, PageB } from './pages'
import { headerStyle } from './Header'
const cardStyle = {
backgroundColor: '#008080'
}
const Stack = createStackNavigator()
function PageNavigator() {
return (
<Stack.Navigator screenOptions={{ cardStyle }}>
<Stack.Screen
name="PAGE_A"
component={PageA}
options={{ title: 'PageA', headerLeft: () => <HeaderLeft /> }}
/>
<Stack.Screen
name="PAGE_B"
component={PageB}
options={{ title: 'PageA' }}
/>
</Stack.Navigator>
);
}
といった感じになります。
変更点を見ていきましょう。
import React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
今まではピュアjsで良かったんですが、あたらしい記法はjsxのシンタックスが必要なので、ファイル名も書き換えてreactをインポートしておきましょう。tsの人はtsxにね!
また、StackNavigatorは@react-navigation/stack
から呼び出します。
さてさて、引数のなくなったcreateStackNavigatorなんですが、空打ちしてから使います。
const Stack = createStackNavigator()
なんかめんどくさいけど、きっとインスタンスを生成させたいのだろうな感。
それではメイン部分を見ていきましょう!
function PageNavigator() {
return (
<Stack.Navigator screenOptions={{ cardStyle }}>
<Stack.Screen
name="PAGE_A"
component={PageA}
options={{ title: 'PageA', headerLeft: () => <HeaderLeft /> }}
/>
<Stack.Screen
name="PAGE_B"
component={PageB}
options={{ title: 'PageA' }}
/>
</Stack.Navigator>
);
}
こんな感じで、今まではオブジェクトで定義していたところはコンポーネントとして定義できます。
なんかずっとこれを待っていた感・・・。悔しいけど感激しちゃう・・・!!
余談ですが、この記法昔僕が大好きだったrnrfとおんなじ感じなんだよなぁ・・・。
直感的でわかりやすいですね!!
一旦、Stack.Navigator
で囲っとかって感じですね。
この時screenOptionsを使うことで子コンポーネントたちに共通のoptionsを与える事ができます。
ここではcardStyleを与えているので、
<Stack.Screen
name="PAGE_A"
component={PageA}
options={{ title: 'PageA', headerLeft: () => <HeaderLeft />, cardStyle }}
/>
としているのと同意義ですね!。
全部に与えたいものがあるときはStack.NavigatorのscreenOptionsを。この画面だけ!ってときはStack.Screenのoptionsを使ってやってください。
一応インターフェースを個人的によく使うよってものだけまとめておきますね!
全部書くととんでもなくなるので、その場合は本家ページを見てください!
NavigatorのAPI
<Stack.Navigator screenOptions={{ cardStyle }}>
プロパティ | 役割 |
---|---|
screenOptions | 子コンポーネントに共通のoptionsを渡すために使用する |
initialRouteName | 最初に描画するScreen名を指定する |
headerMode | ヘッダーの表示方法を指定する Stack限定 |
drawerStyle | ドロワーのスタイルを定義する Drawer限定 |
tabBarOptions | タブバーに与えるオプションを定義する(これ使えばタブのデザイン変えられる) Tab限定 |
さてさて、つぎは画面定義ですね。
画面定義はNavigator.Screenを使って行います。
<Stack.Screen
name="PAGE_B"
component={PageB}
options={{ title: 'PageA' }}
/>
これもすごくわかりやすくてめっちゃいいですね!
おんなじようによく使うやつだけ書き出しておきますね。
ScreenのAPI
プロパティ | 役割 |
---|---|
name | 画面名を指定する |
component | 表示させるコンポーネントを指定する |
options | コンポーネントに与えるオプションを定義する |
こんな感じですね。
ちょっと物足りないかもですが、基本これおぼえておけばだいたい大丈夫です。
AppContainer -> NavigationContainer
AppContainerは廃止です!
かつて(V4)ではAppContainerで最後囲ってあげないといけなかったんです。
例えば、さっきのStackのNavigatorをpageNavigatorという名前にして例を書くと、
import { createAppContainer } from 'react-navigation'
import PageNavigator from './routes/pages/'
const Route = createAppContainer(PageNavigator)
といった具合に。こうするとナビゲーションが成立していました。
だけど、V5からは
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import PageNavigator from './routes/pages/'
function Route() {
return (
<NavigationContainer>
<PageNavigator />
</NavigationContainer>
);
}
という感じですね。
かつて存在していた、onNavigationStateChangeはonStateChangeに変わり、渡される引数も変更されてますのでいい感じに・・・!!
一番めんどくさいのがSwitchNavigator
v4使っている人、思ったよりmigrationめんどくさくないじゃんって思ってません?
ほんとに一番めんどくさいのがSwitchNavigatorです。
これなんとなくなりました・・・!!
これから始める人向けにSwitchNavigatorとは何かというと、認証などで使われるNavigatorになります。
例えば、
- ログインページ(これ以外にも多数)
- ホームページ(これ以外にも多数)
の2つのページがあるとします(2つとしてますが、StackNavigatorのように複数のページで構成されたものをすべて足して1つとカウントしてます)。
これってそれぞれの画面で行き来が簡単にできてはまずいですよね。
「ログイン」、「ログアウト」をちゃんとさせてから画面遷移させたいじゃないですか。そして、ログインしていないときはどういったアプリなのかとかが分かるようなページを見せたい。ホームではコンテンツを閲覧させたい。
そういった状態を元に行けるページを制限するといった用途のときに、SwitchNavigatorを使っていました。これを使うと切り分けがスムーズになるし、遷移させるコードを書かない限り、OSの機能をつかっても戻る事ができなくなります。
なのでそういった用途でこれが使われていました。
しかし、V5からは廃止でしかも代替もないです。
ではどうすればいいのかというと、
JSXで頑張れ(公式曲解)とのこと。
具体的にはContextを使って認証状態を制御してねとのことで、こんな感じになります。
function Route() {
const authContext = React.useContext(AuthContext.Context)
return (
<Stack.Navigator initialRouteName="Login">
{authContext.loggedIn ?
<Stack.Screen name="Home" component={Home} />:
<Stack.Screen name="Login" component={Login} />
}
</Stack.Navigator>
);
}
といった形で、三項演算子で頑張らないといけなくなるんですね・・・。
この書き換えが地味にめんどくさく、色んな状態があったりすると随所書き換えないといけなくなります。
僕の場合はこれがかなりめんどくさかったです・・・。
hooks
hooksを使う場合は@react-navigation/nativeから持ってきます。
import React from 'react';
import {Text, TouchableOpacity} from 'react-native';
import {useNavigation} from '@react-navigation/native';
function Button() {
const {navigate} = useNavigation();
return (
<TouchableOpacity onPress={() => navigate('PAGE_A')} >
<Text>go next</Text>
</TouchableOpacity>
);
}
従来はreact-navigation-hooksから持ってきたものを@react-navigation/nativeに置き換えるだけですね!。
めっちゃかんたんだし、標準対応していてとにかくいい以外に言いようがない感じですね。
使い方は今まで通り、
const {navigate} = useNavigation();
navigateを手に入れて、
navigate('PAGE_A')
ページ名を引数に実行です!。これでPAGE_Aに遷移します。
Drawerのオープン
react-navigation-hooksにはopenDrawerみたいな感じの使いやすものがあったんですが、なくなりました・・・。
なのでv4の人はこの部分も置き換える必要があるとかと思います。
import React from 'react'
import { DrawerActions } from '@react-navigation/routers'
import { useNavigation } from '@react-navigation/native'
import { TouchableOpacity, Text } from 'react-native'
function HeaderLeft() {
const { dispatch } = useNavigation()
const onPress = React.useCallback(() => {
dispatch(DrawerActions.openDrawer())
}, [dispatch])
return (
<TouchableOpacity onPress={onPress}>
<Text>open</Text>
</TouchableOpacity>
)
}
ちょっとめんどくさい書き換えが必要がだったのはこれですね。
@react-navigation/routersからDrawerActionsを持ってきて、
至ってふつうにアクションをdispatchする形式です。これはちょっとめんどいですね・・・。
dispatch(DrawerActions.openDrawer())
おわりに
やりながらメモをしていったので、結構内容端折っているところもあると思います。
要望などございましたら、ぜひぜひ追加しますので要望あればお教えください!!
なにかと結構メモっぽいものになってしまいました・・・・。時間があれば、ちゃんとした使い方をわかりやすいものを作っていきたいです。本の方ではちゃんと書いていますので見ていただけますと幸いです。
所感としてはですね、だいぶ使いやすくなったなとかんじてます。
タブに背景色をつけるとSafeAreaだけ白いままになるというバグがあって色々とめんどくさい手順を踏んでたんですがこれらのバグも解消されててわりと気持ちの良いコードを書けています(今のところは)。
本を書いたらもっとわかりやすい内容を書く時間も生まれてくると思うので、僕が分かる範囲内になりますがこういうの書いてほしいなどあったらぜひお教えください!。
また、全然コードかけないんだけどこれからかけるようになりたいという人を応援していきたいと思うのでめっちゃ入門とかも描きますし、なんなら直接教えに行きますぐらいのテンションでもありますので、まだまだ未熟者ではありますが遠慮なくツイッターなりなんなりご連絡ください。