Expo勉強日記
1. Expo Router
リンク:https://docs.expo.dev/develop/file-based-routing/#what-is-expo-router
1-1. 概要
- ファイルベースのルーティングであるので、Next.jsとかに慣れていると、そのままapp routerのような感じで開発ができる
-
app
ディレクトリ以下がそのままroutingになっている -
app
以下のindex.tsx
ファイルがページを表している - これらは必ず
.js
,.jsx
,.ts
,.tsx
にすること
/settings
というページを作成したい場合
1-2. 例:- TL;DR
-
/app/settings/index.tsx
を作成して、このindexファイルにページを作成すれば良い
-
_layout.tsx
1-3. - Next.jsと同様にして、ディレクトリ以下に共通のレイアウトを適用することができる
-
app/_layout.tsx
にて定義したレイアウトはどのroutesにも適用されるので、あまり思い処理などは書かない方が良い
1-4. Stack navigator
-
アプリでの異なるroutes間のナビゲーションパターンを表す
-
<Stack.Screen>
コンポーネントでルートを追加import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack screenOptions={{ headerStyle: { backgroundColor: '#f4511e', }, headerTintColor: '#fff', headerTitleStyle: { fontWeight: 'bold', }, }}> <Stack.Screen name="index" /> <-- / <Stack.Screen name="details" /> <-- /details </Stack> ); }
-
上を、
<Link>
コンポーネントやuseRouter
を用いて遷移できるimport { Link } from 'expo-router'; import { View, Text, StyleSheet } from 'react-native'; export default function HomeScreen() { return ( <View style={styles.container}> <Text>Home</Text> <Link href="/details">View details</Link> <-- ここを押すと遷移できる </View> ); } ...
-
Link
は基本<Text>
を wrap している -
<Button>
コンポーネントを利用したい場合には、expo-router由来のuseRouter
を用いてonPress
部分で関数を差し込めば良い
-
1-5. Group
- 関連するルートをまとめることができるもの
- 名前の規則として、
(name)
のように名前を丸括弧で括ったディレクトリを利用する
// app/(home)/index.tsx
import { Link, useRouter } from "expo-router";
import { Button, Text, View } from "react-native";
export default function HomePage() {
const router = useRouter()
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>Home</Text>
<Button title="View Details" onPress={() => router.push('/details')} />
</View>
);
}
// app/(home)/_layout.tsx
import { Stack } from "expo-router";
export default function HomeLayout() {
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen name="index" />
<Stack.Screen name="details" />
</Stack>
);
}
// app/(home)/_details.tsx
import { StyleSheet, Text, View } from "react-native";
export default function DetailsPage() {
return (
<View style={styles.containers}>
<Text>Details</Text>
</View>
)
}
const styles = StyleSheet.create({
containers: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
- トップのルートレイアウト部分に
<Stack.Screen name="(home)" />
のようにして明示的にスクリーンの宣言をする
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(home)" />
</Stack>
);
}
Next.jsに慣れていると、Expoでのモバイル開発はかなり簡単で良い。
その反面、学びは少し少ないかもなので、Flutterなどもキャッチアップしたい。
1-6. Tab Navigator
- 特にモバイルアプリであると、指での操作などの観点から、タブでのナビゲーションが結構必要だったりする
- Expoでは、タブでのナビゲーションが簡単に実装できる
- Groupsで
(tabs)
とつくったからといってタブでのナビゲーションができるというわけではなくて、わかりやすいようにこのグループを作っている
- Groupsで
Example
※ツリー構造は以下のようにする。(tabs)でグループを分けて、その下にタブでナビゲーションをしたい項目を並べるみたいな感じ。
-> % tree app
app
├── (tabs)
│ ├── (home)
│ │ ├── _layout.tsx
│ │ ├── details.tsx
│ │ └── index.tsx
│ ├── _layout.tsx
│ └── settings.tsx
└── _layout.tsx
-
app/(tabs)/_layout.tsx
-
Stack
コンポーネントと同じ感じで、Tabs
コンポーネントを利用してタブを作成していく(とても簡単)
import { Tabs } from "expo-router"; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="(home)" /> <Tabs.Screen name="settings" /> </Tabs> ) }
-
-
app/_layout.tsx
- 先ほどまでは、ここに直接
Stack
にてスタックする画面を明示的に記載していたが、(tabs)配下に集約させたので、ここは(tabs)だけスタックさせれば良い
import { Stack } from "expo-router"; export default function RootLayout() { return ( <Stack> <Stack.Screen name=**"(tabs)"** /> </Stack> ); }
- 先ほどまでは、ここに直接
※実際の動作
やっぱ直感的で開発しやすい。
1-7. Not found routes
- 何も見つからない場合、この Not found routesを用いる
- エラーレスポンス 404 の際に用いる
- ファイル名は特別で、
+not-found.tsx
とする -
app
ディレクトリ直下に配置する
Example
-
app/+not-found.tsx
-
Link
コンポーネントですぐにホーム画面に戻れるようにしている
import React from "react"; import { Link, Stack } from "expo-router"; import { StyleSheet, View } from "react-native"; export default function NotFoundScreen() { return ( <> <Stack.Screen options={{ title: 'Not Found' }} /> <View> <Link href="/">Go to Home</Link> </View> </> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' } })
-
-
試しに、(home)の部分の
index.tsx
に、存在しないページへ移動するためのボタンを配置する-
app/(home)/index.tsx
import { useRouter } from "expo-router"; import { Button, Text, View } from "react-native"; export default function HomePage() { const router = useRouter() return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <Text>Home</Text> <Button title="View Details" onPress={() => router.push('/details')} /> <Button title="?" onPress={() => router.push('/aaaaa')} /> </View> ); }
-
app/_layout.tsx
import { Stack } from "expo-router"; export default function RootLayout() { return ( <Stack> <Stack.Screen name="(tabs)" /> <Stack.Screen name="+not-found" /> </Stack> ); }
-
“?” を押すことで、404のページに移動できた
-
公式チュートリアルには、ここの実際にnot foundに行く部分のコードなかったけど、普通に試せた方が良いと思う。
2. Dynamic routes
リンク:https://docs.expo.dev/develop/dynamic-routes/
2-1. Why Dynamic routes?
- ユーザー別の画面を出したいときに、
user_id
などで分けるためにこのルーティングは利用される- 例えば、
user_id=1
のための詳細ページであれば、app/(home)/details/[user_id].tsx
のように[user_id]
部分が動的に変わってルーティングできる
- 例えば、
2-2. Dynamic routeを作成し、id別で遷移する
2-2-1. 準備
-
今回は、
app/(tabs)/(home)/details
配下にDynamic routeを配置する-> % tree app app ├── (tabs) │ ├── (home) │ │ ├── _layout.tsx │ │ ├── details │ │ │ └── [id].tsx. <-- Dynamic route │ │ └── index.tsx │ ├── _layout.tsx │ └── settings.tsx ├── +not-found.tsx └── _layout.tsx
-
app/(tabs)/(home)/details/[id].tsx
(後ほどsearch params別での処理を追加)import { StyleSheet, Text, View } from "react-native"; export default function DetailsPage() { return ( <View style={styles.containers}> <Text>Details</Text> </View> ) } const styles = StyleSheet.create({ containers: { flex: 1, justifyContent: 'center', alignItems: 'center' } })
-
index.tsx
にルーティングを追加する
2-2-2. (home)の -
Button
コンポーネントにて、それぞれid=1
またはid=2
であるときのページへ遷移するボタンを設ける-
app/(tabs)/(home)/index.tsx
import { useRouter } from "expo-router"; import { Button, Text, View } from "react-native"; export default function HomePage() { const router = useRouter() return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <Text>Home</Text> <Button title="View Details 1" onPress={() => router.push('/details/1')} /> <Button title="View Details 2" onPress={() => router.push('/details/2')} /> </View> ); }
-
ただ、このままでは id が異なっていても同じ内容を返す
-
id
別にページの内容を変える
2-2-3. -
このままであると、
id
別にページのコンテンツを変更できないので、app/(tabs)/(home)/details/[id].tsx
に search params を受け取る処理を追加する-
app/(tabs)/(home)/details/[id].tsx
import { useLocalSearchParams } from "expo-router"; import { StyleSheet, Text, View } from "react-native"; export default function DetailsPage() { const { id } = useLocalSearchParams() return ( <View style={styles.containers}> <Text>Details {id}</Text> </View> ) } const styles = StyleSheet.create({ containers: { flex: 1, justifyContent: 'center', alignItems: 'center' } })
-
id
別に表示された-
id=1
-
id=2
-
-
2-2-4. 遷移するためのボタンは統一する
-
今では、 search params毎にボタンが異なっているので、これを統一する
-
search paramsのinputを動的にすれば良い
-
app/(tabs)/(home)/index.tsx
import { useRouter } from "expo-router"; import { Button, Text, View } from "react-native"; export default function HomePage() { const router = useRouter() const id = mockUseFetchId() return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <Text>Home</Text> <Button title="View Details" onPress={() => router.push(`/details/${id}`)} /> </View> ); } const mockUseFetchId = () => { return Math.random().toString(36).substring(7) }
-
-
動的にルーティングできている