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
3.9k
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
380
フロントエンドエンジニアの友人と“型”で話がすれ違った原因 YUMEMI.grow合同LT会in横浜 @Kaito-Dogi
kaito_dogi
1
640
ウォッチフェイス作ってみた shibuya.apk #41 @Kaito-Dogi
kaito_dogi
0
1.3k
Other Decks in Programming
See All in Programming
PHP でアセンブリ言語のように書く技術
memory1994
PRO
1
170
Realtime API 入門
riofujimon
0
150
シェーダーで魅せるMapLibreの動的ラスタータイル
satoshi7190
1
480
AI時代におけるSRE、 あるいはエンジニアの生存戦略
pyama86
6
1.2k
「今のプロジェクトいろいろ大変なんですよ、app/services とかもあって……」/After Kaigi on Rails 2024 LT Night
junk0612
5
2.2k
最新TCAキャッチアップ
0si43
0
190
見せてあげますよ、「本物のLaravel批判」ってやつを。
77web
7
7.8k
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
6
2k
OnlineTestConf: Test Automation Friend or Foe
maaretp
0
120
アジャイルを支えるテストアーキテクチャ設計/Test Architecting for Agile
goyoki
9
3.3k
OSSで起業してもうすぐ10年 / Open Source Conference 2024 Shimane
furukawayasuto
0
110
Duckdb-Wasmでローカルダッシュボードを作ってみた
nkforwork
0
130
Featured
See All Featured
Gamification - CAS2011
davidbonilla
80
5k
Art, The Web, and Tiny UX
lynnandtonic
297
20k
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
42
9.2k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Producing Creativity
orderedlist
PRO
341
39k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Music & Morning Musume
bryan
46
6.2k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.2k
Bash Introduction
62gerente
608
210k
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/
ありがとうございました🙌