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
4.1k
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
260
Kotlin で AWS Lambda 動かしてみた Server-Side Kotlin Meetup vol.9 @Kaito-Dogi
kaito_dogi
1
450
フロントエンドエンジニアの友人と“型”で話がすれ違った原因 YUMEMI.grow合同LT会in横浜 @Kaito-Dogi
kaito_dogi
1
660
ウォッチフェイス作ってみた shibuya.apk #41 @Kaito-Dogi
kaito_dogi
0
1.4k
Other Decks in Programming
See All in Programming
Deep Dive into ~/.claude/projects
hiragram
9
1.6k
Goで作る、開発・CI環境
sin392
0
120
Composerが「依存解決」のためにどんな工夫をしているか #phpcon
o0h
PRO
1
240
来たるべき 8.0 に備えて React 19 新機能と React Router 固有機能の取捨選択とすり合わせを考える
oukayuka
2
860
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
140
What Spring Developers Should Know About Jakarta EE
ivargrimstad
0
290
Cline指示通りに動かない? AI小説エージェントで学ぶ指示書の書き方と自動アップデートの仕組み
kamomeashizawa
1
580
PHP 8.4の新機能「プロパティフック」から学ぶオブジェクト指向設計とリスコフの置換原則
kentaroutakeda
2
650
Node-RED を(HTTP で)つなげる MCP サーバーを作ってみた
highu
0
110
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
240
イベントストーミング図からコードへの変換手順 / Procedure for Converting Event Storming Diagrams to Code
nrslib
1
490
アンドパッドの Go 勉強会「 gopher 会」とその内容の紹介
andpad
0
270
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3k
A designer walks into a library…
pauljervisheath
207
24k
Build your cross-platform service in a week with App Engine
jlugia
231
18k
Making Projects Easy
brettharned
116
6.3k
The World Runs on Bad Software
bkeepers
PRO
69
11k
Documentation Writing (for coders)
carmenintech
72
4.9k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Bash Introduction
62gerente
614
210k
Site-Speed That Sticks
csswizardry
10
660
We Have a Design System, Now What?
morganepeng
53
7.7k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
35
2.4k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
2.9k
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/
ありがとうございました🙌