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
390
フロントエンドエンジニアの友人と“型”で話がすれ違った原因 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
【re:Growth 2024】 Aurora DSQL をちゃんと話します!
maroon1st
0
780
創造的活動から切り拓く新たなキャリア 好きから始めてみる夜勤オペレーターからSREへの転身
yjszk
1
130
tidymodelsによるtidyな生存時間解析 / Japan.R2024
dropout009
1
790
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
3
300
htmxって知っていますか?次世代のHTML
hiro_ghap1
0
340
KMP와 kotlinx.rpc로 서버와 클라이언트 동기화
kwakeuijin
0
160
Monixと常駐プログラムの勘どころ / Scalaわいわい勉強会 #4
stoneream
0
280
menu基盤チームによるGoogle Cloudの活用事例~Application Integration, Cloud Tasks編~
yoshifumi_ishikura
0
110
Spatial Rendering for Apple Vision Pro
warrenm
0
110
テストコードのガイドライン 〜作成から運用まで〜
riku929hr
5
740
Exploring: Partial and Independent Composables
blackbracken
0
100
命名をリントする
chiroruxx
1
420
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Writing Fast Ruby
sferik
628
61k
Building an army of robots
kneath
302
44k
Building Better People: How to give real-time feedback that sticks.
wjessup
365
19k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
It's Worth the Effort
3n
183
28k
Six Lessons from altMBA
skipperchong
27
3.5k
4 Signs Your Business is Dying
shpigford
181
21k
A Philosophy of Restraint
colly
203
16k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
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/
ありがとうございました🙌