Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Expo Router は Expo 導入の決め手となるか フロントエンドカンファレンス沖縄2...
Search
Kaito-Dogi
November 18, 2023
Programming
3
4k
Expo Router は Expo 導入の決め手となるか フロントエンドカンファレンス沖縄2023 @Kaito-Dogi
2023/11/18 開催のフロントエンドカンファレンス沖縄2023にて、『Expo Router は Expo 導入の決め手となるか』 というテーマで発表しました。
Kaito-Dogi
November 18, 2023
Tweet
Share
More Decks by Kaito-Dogi
See All by Kaito-Dogi
Android でも Haptics 入門 potatotips #84 @Kaito-Dogi
kaito_dogi
0
250
Kotlin で AWS Lambda 動かしてみた Server-Side Kotlin Meetup vol.9 @Kaito-Dogi
kaito_dogi
1
420
フロントエンドエンジニアの友人と“型”で話がすれ違った原因 YUMEMI.grow合同LT会in横浜 @Kaito-Dogi
kaito_dogi
1
650
ウォッチフェイス作ってみた shibuya.apk #41 @Kaito-Dogi
kaito_dogi
0
1.3k
Other Decks in Programming
See All in Programming
お前もAI鬼にならないか?👹Bolt & Cursor & Supabase & Vercelで人間をやめるぞ、ジョジョー!👺
taishiyade
5
3.8k
ペアーズでの、Langfuseを中心とした評価ドリブンなリリースサイクルのご紹介
fukubaka0825
2
300
CNCF Project の作者が考えている OSS の運営
utam0k
5
690
一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜
atsumim
0
110
“あなた” の開発を支援する AI エージェント Bedrock Engineer / introducing-bedrock-engineer
gawa
11
1.8k
プログラミング言語学習のススメ / why-do-i-learn-programming-language
yashi8484
0
120
動作確認やテストで漏れがちな観点3選
starfish719
6
1k
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
670
Djangoアプリケーション 運用のリアル 〜問題発生から可視化、最適化への道〜 #pyconshizu
kashewnuts
1
230
『GO』アプリ バックエンドサーバのコスト削減
mot_techtalk
0
130
[JAWS-UG横浜 #80] うわっ…今年のServerless アップデート、少なすぎ…?
maroon1st
1
170
AWS Organizations で実現する、 マルチ AWS アカウントのルートユーザー管理からの脱却
atpons
0
130
Featured
See All Featured
Product Roadmaps are Hard
iamctodd
PRO
50
11k
Writing Fast Ruby
sferik
628
61k
Unsuck your backbone
ammeep
669
57k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
175
51k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
The Language of Interfaces
destraynor
156
24k
Navigating Team Friction
lara
183
15k
Statistics for Hackers
jakevdp
797
220k
Building Adaptive Systems
keathley
40
2.4k
Git: the NoSQL Database
bkeepers
PRO
427
64k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.1k
A designer walks into a library…
pauljervisheath
205
24k
Transcript
Expo Router は Expo 導⼊の決め⼿となるか フロントエンドカンファレンス沖縄2023 @Kaito-Dogi
自己紹介 @Kaito_Dogi @Kaito-Dogi ❏ 株式会社ゆめみ ❏ Android エンジニア ❏ React
Native 挑戦中🔥 ❏ 現地参戦2年⽬😎
React Native で モバイル開発したことありますか?
フロントエンドエンジニアとの開発で React Native を採⽤した
純粋な React Native vs Expo
Expo とは ❏ React Native 開発のオープンソースプラットフォーム ❏ Expo SDK を使⽤してネイティブ機能に簡単にアクセスできる
❏ React Native の開発に追従して Expo が開発される ❏ Expo SDK v49.0.0 に対して React Native v0.72 が対応 https://docs.expo.dev/versions/latest/
Expo とは メリット デメリット ❏ 開発環境の構築が容易 ❏ アプリのデプロイ‧配布が 簡単になる ❏
React Native の更新に ⼀定の安定性が保証される ❏ Expo SDK に含まれない ネイティブ機能に アクセスできない ❏ アプリサイズが増加する ❏ 最新の React Native を 利⽤できない
Expo とは メリット デメリット ❏ 開発環境の構築が容易 ❏ アプリのデプロイ‧配布が 簡単になる ❏
React Native の更新に ⼀定の安定性が保証される ❏ Expo SDK に含まれない ネイティブ機能に アクセスできない ❏ アプリサイズが増加する ❏ 最新の React Native を 利⽤できない
Expo とは メリット デメリット ❏ 開発環境の構築が容易 ❏ アプリのデプロイ‧配布が 簡単になる ❏
React Native の更新に ⼀定の安定性が保証される ❏ Expo SDK に含まれない ネイティブ機能に アクセスできない ❏ アプリサイズが増加する ❏ 最新の React Native を 利⽤できない
Expo を導⼊してみた ❏ プロジェクトの適性 ❏ Expo で制限されるネイティブ機能を使⽤しない(予定) ❏ ⼩‧中規模なプロジェクト ❏
スピード感を重視 ❏ Android‧フロントエンドエンジニアの学習コスト ❏ 作り込みすぎず、顧客の需要を素早く掴めるか ❏ Expo Router をはじめとした、Expo コミュニティの活発さ https://expo.canny.io/
Expo を導⼊してみた ❏ プロジェクトの適性 ❏ Expo で制限されるネイティブ機能を使⽤しない(予定) ❏ 中規模なプロジェクト ❏
スピード感を重視 ❏ Android‧フロントエンドエンジニアの学習コスト ❏ 作り込みすぎず、顧客の需要を素早く掴めるか ❏ Expo Router をはじめとした、Expo コミュニティの活発さ https://expo.canny.io/
本セッションで話すこと‧話さないこと ❏ 話すこと ❏ Expo Router の基本的な使い⽅ ❏ React Navigation(後述)との共通点‧相違点
❏ Expo Router を導⼊して感じたメリット ❏ 話さないこと ❏ React Navigation から Expo Router への移⾏⽅法 ❏ React Native や Expo そのものの込み⼊った話
本セッションで話すこと‧話さないこと ❏ 話すこと ❏ Expo Router の基本的な使い⽅ ❏ React Navigation(後述)との共通点‧相違点
❏ Expo Router を導⼊して感じたメリット ❏ 話さないこと ❏ React Navigation から Expo Router への移⾏⽅法 ❏ React Native や Expo そのものの込み⼊った話
本セッションで話すこと‧話さないこと ❏ 話すこと ❏ Expo Router の基本的な使い⽅ ❏ React Navigation(後述)との共通点‧相違点
❏ Expo Router を導⼊して感じたメリット ❏ 話さないこと ❏ React Navigation から Expo Router への移⾏⽅法 ❏ React Native や Expo そのものの込み⼊った話
❏ React Native の新しい画⾯遷移ライブラリ ❏ ファイルシステムベースルーティングが採⽤されている ❏ React Navigation 上に構築されている
❏ React Native の画⾯遷移ライブラリ ❏ Expo SDK v49 に対して Expo Router v2 を使⽤できる ❏ Expo SDK v50 に対して Expo Router v3 が予定されている Expo Router とは
❏ React Native の新しい画⾯遷移ライブラリ ❏ ファイルシステムベースルーティングが採⽤されている ❏ React Navigation 上に構築されている
❏ React Native の画⾯遷移ライブラリ ❏ Expo SDK v49 に対して Expo Router v2 を使⽤できる ❏ Expo SDK v50 に対して Expo Router v3 が予定されている Expo Router とは
Expo Router の特徴 ❏ スクリーンの描画 ❏ そのディレクトリの _layout.tsx がまず描画される ❏
次にそのディレクトリの index.tsx が描画される ❏ ディレクトリ構成をもとに、Stack、Tabs が⾃動的に構築される ❏ <Link /> コンポーネント、router オブジェクト、useRouter で画⾯遷移 ❏ “/hoge” は “app/hoge.tsx”、“app/events/hoge.tsx” ❏ どの画⾯も⾃動でディープリンク可能に(マッピング不要) https://docs.expo.dev/routing/introduction/
チケット管理アプリを題材に🎟
https://front-okinawa.connpass.com/ Expo Router React Navigation
Expo Router React Navigation
チケット管理アプリ ❏ 2つのタブで構成 ❏ スタックでイベント詳細へ ❏ モーダルで QR コードを表⽰
チケット管理アプリ ❏ 2つのタブで構成 ❏ スタックでイベント詳細へ ❏ モーダルで QR コードを表⽰
チケット管理アプリ ❏ 2つのタブで構成 ❏ スタックでイベント詳細へ ❏ モーダルで QR コードを表⽰
チケット管理アプリ ❏ 2つのタブで構成 ❏ スタックでイベント詳細へ ❏ モーダルで QR コードを表⽰
❏ パッケージのインストール ❏ 設定の変更 ❏ エントリーポイントの変更 ❏ app.json に scheme
を追加 ❏ babel.config.js の plugins に追加 ❏ App.tsx を削除 Expo Router のセットアップ https://docs.expo.dev/routing/installation/
❏ “expo-router” をインストール ❏ Expo Router セットアップ済みのテンプレートも パッケージのインストール % npx
expo install expo-router Terminal % npx expo install expo-router Terminal
エントリーポイントの変更 - "main": "node_modules/expo/AppEntry.js", + "main": "expo-router/entry", package.json
❏ ディープリンクで使⽤される app.json に scheme を追加 { ..., + "scheme":
"expo-router-sample", } app.json
module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"],
plugins: [ ..., + "expo-router/babel" ], }; }; babel.config.js の plugins に追加 babel.config.js
Layout routes ❏ そのディレクトリの _layout.tsx に記述する ❏ ページ共通で描画したいコンポーネントを描画できる ❏ Header、Footer、Context
API など import { Slot } from "expo-router"; export default function Layout() { return <Slot />; } app/_layout.tsx https://docs.expo.dev/routing/layouts/
Layout routes import { Tabs } from "expo-router"; import {
TicketProvider } from "@/src/contexts/Ticket"; export default function Layout() { return ( <TicketProvider> <Tabs>...</Tabs> </TicketProvider> ); } app/_layout.tsx 購⼊済みチケットを注⼊
❏ Navigator の外側に配置する import { TicketProvider } from "./src/contexts/Ticket"; import
{ AppNavigator } from "./src/navigation"; export default function App() { return ( <TicketProvider> <AppNavigator /> </TicketProvider> ); } 共通レイアウトの配置 App.tsx 購⼊済みチケットを注⼊
❏ 画⾯が重なっていくような画⾯遷移 ❏ React Navigation の Native Stack Navigator をラップしている
❏ そのディレクトリの index.tsx がまず描画される Stack https://docs.expo.dev/router/advanced/stack/ https://reactnavigation.org/docs/native-stack-navigator/ import { Stack } from "expo-router"; export default function Layout() { return <Stack />; } app/_layout.tsx
Stack ❏ <Stack /> コンポーネントでスタック全体の設定 ❏ <Stack.Screen /> コンポーネントで画⾯ごとの設定 import
{ Stack } from "expo-router"; export default function Layout() { return ( <Stack screenOptions={{ headerShown: false }}> <Stack.Screen name="index" /> ... </Stack> ); } app/events/_layout.tsx
import { createNativeStackNavigator } from "@react-navigation/native-stack"; export type EventStackParamList =
{ EventList: undefined; ... }; const Stack = createNativeStackNavigator<EventStackParamList>(); export const EventStack: FC = () => { return ( <Stack.Navigator initialRouteName={"EventList"} > <Stack.Screen name={"EventList"} component={EventListScreen} /> ... </Stack.Navigator> ); }; Native Stack Navigator navigation/EventStack.tsx
import { createNativeStackNavigator } from "@react-navigation/native-stack"; export type EventStackParamList =
{ EventList: undefined; ... }; const Stack = createNativeStackNavigator<EventStackParamList>(); export const EventStack: FC = () => { return ( <Stack.Navigator initialRouteName={"EventList"} > <Stack.Screen name={"EventList"} component={EventListScreen} /> ... </Stack.Navigator> ); }; Native Stack Navigator navigation/EventStack.tsx そのスタックの画⾯を 型として定義
import { createNativeStackNavigator } from "@react-navigation/native-stack"; export type EventStackParamList =
{ EventList: undefined; ... }; const Stack = createNativeStackNavigator<EventStackParamList>(); export const EventStack: FC = () => { return ( <Stack.Navigator initialRouteName={"EventList"} > <Stack.Screen name={"EventList"} component={EventListScreen} /> ... </Stack.Navigator> ); }; Native Stack Navigator navigation/EventStack.tsx Navigator を初期化
import { createNativeStackNavigator } from "@react-navigation/native-stack"; export type EventStackParamList =
{ EventList: undefined; ... }; const Stack = createNativeStackNavigator<EventStackParamList>(); export const EventStack: FC = () => { return ( <Stack.Navigator initialRouteName={"EventList"} > <Stack.Screen name={"EventList"} component={EventListScreen} /> ... </Stack.Navigator> ); }; Native Stack Navigator navigation/EventStack.tsx 各画⾯に対して Component を指定
import { createNativeStackNavigator } from "@react-navigation/native-stack"; export type EventStackParamList =
{ EventList: undefined; ... }; const Stack = createNativeStackNavigator<EventStackParamList>(); export const EventStack: FC = () => { return ( <Stack.Navigator initialRouteName={"EventList"} > <Stack.Screen name={"EventList"} component={EventListScreen} /> ... </Stack.Navigator> ); }; Native Stack Navigator navigation/EventStack.tsx 最初に描画する 画⾯を指定
Dynamic routes ❏ [id].tsx で任意の id にマッチできる ❏ useLocalSearchParams でパラメータを取得できる
https://docs.expo.dev/routing/create-pages/#dynamic-routes https://docs.expo.dev/router/reference/hooks/#uselocalsearchparams import { Redirect, useLocalSearchParams } from "expo-router"; export default function Page() { const { id } = useLocalSearchParams(); if (typeof id !== "string") return <Redirect href="/404" />; return <EventDetailScreen id={id} />; } app/events/[id].tsx
❏ <Link /> コンポーネントでは、Href オブジェクトで pathname、params を指定する import { Link
} from 'expo-router'; export default function Page() { ... <Link href={{ pathname: "/events/[id]", params: { id: "1" } }}> ... </Link> ... } Dynamic routes https://docs.expo.dev/routing/navigating-pages/#linking-to-dynamic-routes app/index.tsx
❏ route オブジェクトの params から取得する import { RouteProp } from
"@react-navigation/native"; type Props = { route: RouteProp<EventStackParamList, "EventDetail">; }; export const EventDetailScreen: FC<Props> = ({ route }) => { const { id } = route.params; ... Passing parameters to routes https://reactnavigation.org/docs/params/ components/screens/EventDetailScreen/EventDetailScreen.tsx
❏ パラメータの型を定義する ❏ パラメータが不要の場合は undefined とする import { createNativeStackNavigator }
from "@react-navigation/native-stack"; type EventStackParamList = { EventList: undefined; EventDetail: { id: string }; }; const Stack = createNativeStackNavigator<EventStackParamList>(); Passing parameters to routes https://reactnavigation.org/docs/typescript/#type-checking-screens navigation/EventStack.tsx
Tabs ❏ iOS の Tab bars や Android の Navigation
bar ❏ React Navigation の Bottom Tabs をラップしている ❏ そのディレクトリの index.tsx がまず描画される https://docs.expo.dev/router/advanced/tabs/ https://reactnavigation.org/docs/bottom-tab-navigator/ import { Tabs } from "expo-router"; export default function Layout() { return <Tabs />; } app/_layout.tsx
Tabs ❏ <Tabs /> コンポーネントでスタック全体の設定 ❏ <Tabs.Screen /> コンポーネントで画⾯ごとの設定 import
{ Tabs } from "expo-router"; export default function Layout() { return ( <Tabs screenOptions={{ headerShown: false }}> <Tabs.Screen name="events" /> </Tabs> ); } app/_layout.tsx
Tabs export default function Layout() { return ( <Tabs screenOptions={{
tabBarActiveTintColor: colors.primary }} > <Tabs.Screen name="events" options={{ title: "イベント", tabBarIcon: ({ color, focused }) => ( <PeopleIcon color={color as `#${string}`} outline={!focused} /> ), }} /> ... </Tabs> ); } app/_layout.tsx
Tabs export default function Layout() { return ( <Tabs screenOptions={{
tabBarActiveTintColor: colors.primary }} > <Tabs.Screen name="events" options={{ title: "イベント", tabBarIcon: ({ color, focused }) => ( <PeopleIcon color={color as `#${string}`} outline={!focused} /> ), }} /> ... </Tabs> ); } app/_layout.tsx 選択されたタブの アイコン‧タイトルの⾊
Tabs export default function Layout() { return ( <Tabs screenOptions={{
tabBarActiveTintColor: colors.primary }} > <Tabs.Screen name="events" options={{ title: "イベント", tabBarIcon: ({ color, focused }) => ( <PeopleIcon color={color as `#${string}`} outline={!focused} /> ), }} /> ... </Tabs> ); } app/_layout.tsx タブのタイトル
Tabs export default function Layout() { return ( <Tabs screenOptions={{
tabBarActiveTintColor: colors.primary }} > <Tabs.Screen name="events" options={{ title: "イベント", tabBarIcon: ({ color, focused }) => ( <PeopleIcon color={color as `#${string}`} outline={!focused} /> ), }} /> ... </Tabs> ); } app/_layout.tsx タブのアイコン
Tabs ❏ href で遷移する画⾯を明⽰的に指定 ❏ href を null にすると、タブとして表⽰されない import
{ Tabs } from "expo-router"; export default function Layout() { return ( <Tabs> <Tabs.Screen name="events" options={{ href: "events" }} /> <Tabs.Screen name="index" options={{ href: null }} /> </Tabs> ); } app/_layout.tsx
import { Tabs } from "expo-router"; export default function Layout()
{ return ( <Tabs> <Tabs.Screen name="events" options={{ href: "events" }} /> <Tabs.Screen name="index" /> </Tabs> ); } Tabs ❏ href で遷移する画⾯を選択 ❏ href を null にすると、タブとして表⽰されない app/_layout.tsx
❏ href で遷移する画⾯を選択 ❏ href を null にすると、タブとして表⽰されない import {
Tabs } from "expo-router"; export default function Layout() { return ( <Tabs> <Tabs.Screen name="events" options={{ href: "events" }} /> <Tabs.Screen name="index" options={{ href: null }} /> </Tabs> ); } Tabs app/_layout.tsx
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; type RootTabParamList = {
Event: undefined; ... }; const Tab = createBottomTabNavigator<RootTabParamList>(); export const RootTab: FC = () => { return ( <Tab.Navigator initialRouteName="Event" > <Tab.Screen name="Event" component={EventStack} /> ... </Tab.Navigator> ); }; Bottom Tabs navigation/RootTab.tsx
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; type RootTabParamList = {
Event: undefined; ... }; const Tab = createBottomTabNavigator<RootTabParamList>(); export const RootTab: FC = () => { return ( <Tab.Navigator initialRouteName="Event" > <Tab.Screen name="Event" component={EventStack} /> ... </Tab.Navigator> ); }; Bottom Tabs navigation/RootTab.tsx そのタブの画⾯を型として定義
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; type RootTabParamList = {
Event: undefined; ... }; const Tab = createBottomTabNavigator<RootTabParamList>(); export const RootTab: FC = () => { return ( <Tab.Navigator initialRouteName="Event" > <Tab.Screen name="Event" component={EventStack} /> ... </Tab.Navigator> ); }; Bottom Tabs navigation/RootTab.tsx Navigator を初期化
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; type RootTabParamList = {
Event: undefined; ... }; const Tab = createBottomTabNavigator<RootTabParamList>(); export const RootTab: FC = () => { return ( <Tab.Navigator initialRouteName="Event" > <Tab.Screen name="Event" component={EventStack} /> ... </Tab.Navigator> ); }; Bottom Tabs navigation/RootTab.tsx 各画⾯に対して Component を指定
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; type RootTabParamList = {
Event: undefined; ... }; const Tab = createBottomTabNavigator<RootTabParamList>(); export const RootTab: FC = () => { return ( <Tab.Navigator initialRouteName="Event" > <Tab.Screen name="Event" component={EventStack} /> ... </Tab.Navigator> ); }; Bottom Tabs navigation/RootTab.tsx 最初に描画する 画⾯を指定
Issue #763 initialRouteName が動かない https://github.com/expo/router/issues/763
Issue #763 initialRouteName が動かない https://github.com/expo/router/issues/763
Issue #763 initialRouteName が動かない https://github.com/expo/router/issues/763
Issue #763 initialRouteName が動かない ❏ 最初に表⽰される index.tsx でリダイレクト https://docs.expo.dev/router/reference/redirects/ import
{ Redirect } from "expo-router"; export default function Page() { return <Redirect href="/events" />; } app/index.tsx
Modals ❏ <Stack.Screen /> の presentation で “modal” を指定する https://docs.expo.dev/router/advanced/modals/
import { Stack } from "expo-router"; export default function Layout() { return ( <Stack> ... <Stack.Screen name="[id]" options={{ presentation: "modal" }} /> </Stack> ); } app/_layout.tsx
Issue #640 Android で Modals が動かない https://github.com/expo/router/issues/640
Issue #640 Android で Modals が動かない ❏ JS Stack Navigator
で実装する ❏ Material Design として適切ではないためという意⾒もあり、 Android で Modals を使うかは議論が必要 https://docs.expo.dev/router/advanced/stack/#javascript-stack-with-react-navigationstack https://developer.apple.com/design/human-interface-guidelines/modality
❏ JsStack を初期化 import { ParamListBase, StackNavigationState } from "@react-navigation/native";
import { createStackNavigator, StackNavigationEventMap, StackNavigationOptions, TransitionPresets, } from "@react-navigation/stack"; import { withLayoutContext } from "expo-router"; const { Navigator } = createStackNavigator(); const JsStack = withLayoutContext< StackNavigationOptions, typeof Navigator, StackNavigationState<ParamListBase>, StackNavigationEventMap >(Navigator); Issue #640 Android で Modals が動かない app/tickets/_layout.tsx
❏ <JsStack.Screen /> の presentation で “modal” を指定 Issue #640
Android で Modals が動かない export default function Layout() { return ( <JsStack> <JsStack.Screen name="[id]" options={{ ...TransitionPresets.ModalPresentationIOS, presentation: "modal", }} /> </JsStack> ); } app/tickets/_layout.tsx
❏ <JsStack.Screen /> の presentation で “modal” を指定 Issue #640
Android で Modals が動かない export default function Layout() { return ( <JsStack> <JsStack.Screen name="[id]" options={{ ...TransitionPresets.ModalPresentationIOS, presentation: "modal", }} /> </JsStack> ); } app/tickets/_layout.tsx
Typed routes ❏ 画⾯の絶対パスをユニオン型として⽣成してくれる // prettier-ignore type StaticRoutes = `/`
| `/_layout` | `/events/_layout` | `/events/` | `/tickets/_layout` | `/tickets/`; // prettier-ignore type DynamicRoutes<T extends string> = `/events/${SingleRoutePart<T>}` | `/${CatchAllRoutePart<T>}` | `/tickets/${SingleRoutePart<T>}`; // prettier-ignore type DynamicRouteTemplate = `/events/[id]` | `/[...unmatched]` | `/tickets/[id]`; .expo/types/router.d.ts
❏ Expo Router v2(Expo SDK v49)では experimental ❏ app.json で
Typed routes を有効化 { "expo": { ... + "experiments": { + "typedRoutes": true + } } } app.json Typed routes https://docs.expo.dev/router/reference/typed-routes/
❏ ParamList を定義する ❏ その Navigator 内でネストされた Navigator、画⾯の名前を 型付けするのみ(グローバルに遷移できるわけではない) export
type EventStackParamList = { EventList: undefined; EventDetail: { id: string }; }; Type checking with TypeScript navigation/EventStack.tsx https://reactnavigation.org/docs/typescript/
Unmatched routes ❏ [...unmatched].tsx でカスタマイズできる ❏ レスト構⽂(...)を使⽤していれば名前は⾃由 ❏ “/404” で遷移できる
❏ Expo Router v3 から Not found routes が出る予定 ❏ +not-found.tsx に記述する ❏ ネストされたレベルから全てのルートにマッチ https://docs.expo.dev/routing/error-handling/#unmatched-routes https://docs.expo.dev/router/reference/not-found/
Unmatched routes ❏ [...unmatched].tsx でカスタマイズできる ❏ レスト構⽂(...)を使⽤していれば名前は⾃由 ❏ “/404” で遷移できる
❏ Expo Router v3 から Not found routes が出る予定 ❏ +not-found.tsx に記述する ❏ ネストされたレベルから全てのルートにマッチ https://docs.expo.dev/routing/error-handling/#unmatched-routes https://docs.expo.dev/router/reference/not-found/
Top-level src directory ❏ src ディレクトリを app に含められる ❏ src/app
はルートの app よりも優先される ❏ config ファイル、public ディレクトリはルートに置く ❏ 開発中に移動した場合、キャッシュをクリア https://docs.expo.dev/router/reference/src-directory/ % npx expo start --clear Terminal
その他の機能👀
❏ グループ構⽂ “()” ❏ URL にセグメントが表⽰されない ❏ app/auth/home.tsx は “app/auth/home”
にマッチ ❏ app/(auth)/home.tsx は “app/home” にマッチ ❏ useSegments でグループ名の⽂字列を取得できる Groups https://docs.expo.dev/routing/layouts/#groups https://docs.expo.dev/router/reference/hooks/#usesegments
Deep linking ❏ どの画⾯も⾃動でディープリンク可能に ❏ マッピング不要 ❏ その他の実装は従来通り必要(割愛) https://docs.expo.dev/guides/deep-linking/
Deep linking ❏ 画⾯とディープリンクをマッピングする必要がある https://reactnavigation.org/docs/deep-linking/ import { LinkingOptions, NavigationContainer }
from "@react-navigation/native"; const linking: LinkingOptions<ReactNavigation.RootParamList> = { prefixes: [ ... ], config: { screens: { ... }, }, }; export const AppNavigator: FC = () => { return ( <NavigationContainer linking={linking}> ... </NavigationContainer> ); }; navigation/AppNavigator.tsx
API routes https://blog.expo.dev/rfc-api-routes-cce5a3b9f25d https://github.com/expo/expo/pull/24429 import { ExpoRequest, ExpoResponse } from
'expo-router/server'; export async function POST(req: ExpoRequest): Promise<ExpoResponse> { ... return ExpoResponse.json(json); } app/+api.tsx ❏ API(サーバーサイドロジック)をプロジェクト内で実装できる ❏ +api.ts の接尾辞のファイルで作成 ❏ HTTP メソッドが⼀致したときに関数が実⾏される ❏ Expo Router v3 で beta 版リリース予定
Expo Router を使⽤して感じたメリット ❏ ファイルシステムベースルーティングの恩恵 ❏ 型定義やオブジェクトの初期化の必要がなく、簡潔に書ける ❏ Typed routes
がないと扱いづらい ❏ Hooks API が便利 ❏ React Navigation では Screen ⽤の Component に ScreenProps (navigation や route オブジェクト)を渡す必要がある ❏ React Navigation との互換性があり、乗り換えやすい
まとめ ❏ Expo 導⼊の背景に Expo Router をはじめとした Expo コミュニティの活発さがあった ❏
ファイルシステムベースルーティングを採⽤しており、 簡潔に書けるようになった ❏ Expo Router は React Navigation をラップしており、React Navigation との互換性がある
参考記事 ❏ Expo Documentation https://docs.expo.dev/ ❏ React Navigation https://reactnavigation.org/ ❏
Expo Feedback https://expo.canny.io/ ❏ expo/router https://github.com/expo/router ❏ Human Interface Guidelines | Apple Developer Documentation https://developer.apple.com/design/human-interface-guidelines ❏ Material Design https://m3.material.io/ ❏ Evenline - Event Booking App UI Kit https://ui8.net/unpixel/products/evenline---event-booking-app-ui-kit
12/09(⼟)に渋⾕で LT &交流会を開催します🎉 https://coopello2.connpass.com/event/301314/
ありがとうございました🙌