Expo Routerで始めるNative & Webアプリケーションの快適なRouting
こんにちは!テラーノベルでiOS/Android/Webとフロントエンド周りを担当している @kazutoyoです!
以前にもExpoについての記事を書きましたが、今回はExpo(React Native)でのナビゲーション周りを快適に設定ができるExpo Routerについて紹介します。
Expo Routerとは
Expo RouterはNext.jsなどでも採用されているファイルシステムベースでのルーティング機能を提供します。
Expo RouterはReact Nativeで利用されることの多いReact Navigationを利用して構築されており、素のReact Navigationを使うより簡潔にナビゲーションの管理を行うことができます。
また7月に登場しましたExpo Router v2では、ネイティブだけではなくWebのための機能も多く追加されました。
使ってみる
Expo Routerのデモとして用意されているNoteアプリを参考に説明します。
実行すると、このようにノートを追加しリストとして表示されます。
(Webでエラーログが出たりしていますが…)
ディレクトリの構成は次のようになっています。
app/
配下に配置することで、ルーティングが作られます。
またWebの場合はそのパスでルーティングされます。
.
├── app
│ ├── (app)
│ │ ├── _layout.tsx
│ │ ├── compose.tsx
│ │ ├── index.tsx
│ │ └── note
│ │ └── [note].tsx
│ ├── (auth)
│ │ └── sign-in.tsx
│ ├── +html.tsx
│ └── _layout.tsx
├── app.json
├── babel.config.js
├── context
│ ├── auth.tsx
│ └── notes.tsx
├── etc
│ ├── button.tsx
│ └── urlBar.tsx
├── index.js
├── metro.config.js
├── package.json
├── splash.png
└── tsconfig.json
Dynamic routes
app/
ディレクトリにある [note]
はパスパラメータを表現します。(このあたりはNext.jsと同じですね)
[note].tsx
では次のように useLocalSearchParams
でパスパラメータを取得することができます。
import { useLocalSearchParams } from 'expo-router';
// ~~~
const { note } = useLocalSearchParams();
または usePathname hooksでページのパスのみ取得することもできます。
ページの遷移
Dynamic routesのページに遷移する際は params
をセットします。
<Link
href={{
pathname: "/(app)/note/[note]",
params: {
note: item.id,
},
}}
>
</Link>
Link
コンポーネントではなく、 useRouter
hooksで遷移する場合も同様です。
const router = useRouter();
router.push({
pathname: "/(app)/note/[note]",
params: {
note: item.id,
},
});
また、パスパラメータ以外にもクエリパラメータもparamsにセットすることができます。
<Link
href={{
pathname: "/(app)/note/[note]",
params: {
note: item.id,
key1: "value1"
},
}}
/>
~~~
const { note, key1 } = useLocalSearchParams();
Layout Root
_layout.(js|tsx)
ファイルを定義することで、デフォルトのレイアウトを設定することができます。
例えばContextのProviderやTheme、ヘッダー/フッターなどの設定を行います。
ディレクトリごとに _layout
を配置することができ、そのディレクトリ配下のファイルに対してレイアウトが設定されます。
Groups
app/
ディレクトリ上に、 (app)
や (auth)
といった ()
で囲まれたディレクトリがあります。
これはURLのセグメントに含まれることがなくページをグルーピングすることができます。
例えば次のようなURLでページがルーティングされます
-
(app)/compose
→/compose
-
(auth)/sign-in
→/sign-in
Modal
_layout.tsx
で Stack
階層下の Stack.Screen
で presentation: "modal"
オプションを指定します。
DeepLink
Expo Routerで設定したルーティングで、アプリのDeepLinkにも対応しています。
例えばデモアプリでは bacontext://note/{noteId}
で詳細ページを開くことができます。
Webの対応
アプリをWebページとして出力する際の設定が可能となっています。
Root HTML
+html.tsx
でHTMLとして書き出すときのルートのHTMLをカスタマイズすることができます。
こちらで共通のメタタグなど、Head要素に追加する値をセットします。
Meta Tags
<Head />
コンポーネントを利用することで、ページごとにHTMLの <head />
に追加することができます。
例えば、次のようにページのタイトルやmeta descriptionをセットできます。
import Head from 'expo-router/head';
import { Text } from 'react-native';
export default function Page() {
return (
<>
<Head>
<title>My Blog Website</title>
<meta name="description" content="This is my blog." />
</Head>
<Text>About my blog</Text>
</>
);
}
CSS
Expo SDK 49からWeb用にCSSがサポートされました。
Tailwindも利用が可能で、Web用のViewについてCSSでスタイリングを行えます。
Dynamic Routes
動的なページに関しては出力するページを指定をする必要があります。
Next.jsと同じように、 generateStaticParams functionで、生成するページの情報を返すようにします。
import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export async function generateStaticParams(): Promise<Record<string, string>[]> {
const posts = await getPosts();
// Return an array of params to generate static HTML files for.
// Each entry in the array will be a new page.
return posts.map(post => ({ id: post.id }));
}
export default function Page() {
const { id } = useLocalSearchParams();
return <Text>Post {id}</Text>;
}
Expo Router v2の実験的な機能機能
Typed Routes
Expo Router v2ではExperimentalですが、ルーティングのパスをType-Safeに記述することができます。
Async routes
開発時ににReact Suspenseを使用して、JS Bundleを分割することができます。
これにより必要なページのみのJS Bundleのみが読込みされるため、特に大規模なアプリの場合に開発効率が良くなります。
まとめ
Expo Routerはネイティブだけではなく、Webにも対応したユニバーサルで高機能なルーティングライブラリとなっています。
次期バージョンとして api-routes機能が導入される可能性もあり、よりWeb面の強化(SSRなど)されそうです。
ネイティブアプリだけではなく、Webでも展開したいようなサービスを最速でリリースしたいときなど、Expo + Expo Routerを使うのは一つの選択肢になるかもしれませんね! 💪
Discussion