🏄

Expo Routerで始めるNative & Webアプリケーションの快適なRouting

2023/08/10に公開

こんにちは!テラーノベルでiOS/Android/Webとフロントエンド周りを担当している @kazutoyoです!

以前にもExpoについての記事を書きましたが、今回はExpo(React Native)でのナビゲーション周りを快適に設定ができるExpo Routerについて紹介します。
https://zenn.dev/tellernovel_inc/articles/2628a04a22dbf3

Expo Routerとは

Expo RouterはNext.jsなどでも採用されているファイルシステムベースでのルーティング機能を提供します。

Expo RouterはReact Nativeで利用されることの多いReact Navigationを利用して構築されており、素のReact Navigationを使うより簡潔にナビゲーションの管理を行うことができます。

また7月に登場しましたExpo Router v2では、ネイティブだけではなくWebのための機能も多く追加されました。

Announcing Expo Router v2
Announcing Expo Router v2

使ってみる

Expo Routerのデモとして用意されているNoteアプリを参考に説明します。
実行すると、このようにノートを追加しリストとして表示されます。
(Webでエラーログが出たりしていますが…)
Note App

ディレクトリの構成は次のようになっています。
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();

https://docs.expo.dev/routing/create-pages/#dynamic-routes

Layout Root

_layout.(js|tsx) ファイルを定義することで、デフォルトのレイアウトを設定することができます。
例えばContextのProviderやTheme、ヘッダー/フッターなどの設定を行います。
https://github.com/expo/router/blob/main/apps/demo/app/_layout.tsx

ディレクトリごとに _layout を配置することができ、そのディレクトリ配下のファイルに対してレイアウトが設定されます。

https://docs.expo.dev/routing/layouts/#create-a-layout-route

Groups

app/ ディレクトリ上に、 (app)(auth) といった () で囲まれたディレクトリがあります。
これはURLのセグメントに含まれることがなくページをグルーピングすることができます。

例えば次のようなURLでページがルーティングされます

  • (app)/compose/compose
  • (auth)/sign-in/sign-in

https://docs.expo.dev/routing/layouts/#groups

_layout.tsxStack 階層下の Stack.Screenpresentation: "modal" オプションを指定します。

https://github.com/expo/router/blob/expo-router%402.0.1/apps/demo/app/(app)/_layout.tsx#L40-L49

https://docs.expo.dev/router/advanced/modals/

Expo Routerで設定したルーティングで、アプリのDeepLinkにも対応しています。
例えばデモアプリでは bacontext://note/{noteId} で詳細ページを開くことができます。

Webの対応

アプリをWebページとして出力する際の設定が可能となっています。

Root HTML

+html.tsx でHTMLとして書き出すときのルートのHTMLをカスタマイズすることができます。
こちらで共通のメタタグなど、Head要素に追加する値をセットします。
https://github.com/expo/router/blob/expo-router%402.0.1/apps/demo/app/%2Bhtml.tsx
https://docs.expo.dev/router/reference/static-rendering/#root-html

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でスタイリングを行えます。

https://docs.expo.dev/versions/v49.0.0/config/metro/#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>;
}

https://docs.expo.dev/router/reference/static-rendering/#dynamic-routes

Expo Router v2の実験的な機能機能

Typed Routes

Expo Router v2ではExperimentalですが、ルーティングのパスをType-Safeに記述することができます。

https://docs.expo.dev/router/reference/typed-routes/

Async routes

開発時ににReact Suspenseを使用して、JS Bundleを分割することができます。
これにより必要なページのみのJS Bundleのみが読込みされるため、特に大規模なアプリの場合に開発効率が良くなります。

https://docs.expo.dev/router/reference/async-routes/

まとめ

Expo Routerはネイティブだけではなく、Webにも対応したユニバーサルで高機能なルーティングライブラリとなっています。
次期バージョンとして api-routes機能が導入される可能性もあり、よりWeb面の強化(SSRなど)されそうです。

ネイティブアプリだけではなく、Webでも展開したいようなサービスを最速でリリースしたいときなど、Expo + Expo Routerを使うのは一つの選択肢になるかもしれませんね! 💪

テラーノベル テックブログ

Discussion