Expoチュートリアルやるだけ

これやる
※ 以下のコードスニペットはチュートリアルを貼り付けてるだけ。

チュートリアルどおりセットアップする
npx create-expo-app@latest StickerSmash
cd StickerSmash
StickerSmash git:(master) tree -L 2 -I "node_modules"
.
├── README.md
├── app
│ ├── (tabs)
│ ├── +not-found.tsx
│ └── _layout.tsx
├── app.json
├── assets
│ ├── fonts
│ └── images
├── components
│ ├── Collapsible.tsx
│ ├── ExternalLink.tsx
│ ├── HapticTab.tsx
│ ├── HelloWave.tsx
│ ├── ParallaxScrollView.tsx
│ ├── ThemedText.tsx
│ ├── ThemedView.tsx
│ └── ui
├── constants
│ └── Colors.ts
├── eslint.config.js
├── hooks
│ ├── useColorScheme.ts
│ ├── useColorScheme.web.ts
│ └── useThemeColor.ts
├── package-lock.json
├── package.json
├── scripts
│ └── reset-project.js
└── tsconfig.json
公式が用意してるassetsをダウンロードして
assets/imagesに入れた。
名前が被ってるものは何も考えず置換した

reset-project スクリプトを実行
最低限のものだけ残してあとはapp-example
にいったん退避するスクリプトらしい。
最初はこれ叩いて後から必要なものをapp-exampleからコピーしてくるといいよってことかな?
npm run reset-project
> stickersmash@1.0.0 reset-project
> node ./scripts/reset-project.js
Do you want to move existing files to /app-example instead of deleting them? (Y/n): Y
📁 /app-example directory created.
➡️ /app moved to /app-example/app.
➡️ /components moved to /app-example/components.
➡️ /hooks moved to /app-example/hooks.
➡️ /constants moved to /app-example/constants.
➡️ /scripts moved to /app-example/scripts.
📁 New /app directory created.
📄 app/index.tsx created.
📄 app/_layout.tsx created.
✅ Project reset complete. Next steps:
1. Run `npx expo start` to start a development server.
2. Edit app/index.tsx to edit the main screen.
3. Delete the /app-example directory when you're done referencing it.
すっきりした
tree -L 2 -I "node_modules"
.
├── README.md
├── app
│ ├── _layout.tsx
│ └── index.tsx
├── app-example
│ ├── app
│ ├── components
│ ├── constants
│ ├── hooks
│ └── scripts
├── app.json
├── assets
│ ├── fonts
│ └── images
├── eslint.config.js
├── package-lock.json
├── package.json
└── tsconfig.json
11 directories, 8 files

Expo GOでアプリ起動
npx expo start
コマンド表が出てくる
iでios
aでandroid
それぞれ10秒ほどで起動した。早い!

チュートリアルのコードペタって貼ってみる。
cssのようにかける
import { Text, View, StyleSheet } from 'react-native';
export default function Index() {
return (
<View style={styles.container}>
<Text style={styles.text}>Home screen</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: '#fff',
},
});

スタックに新しいスクリーンを追加する
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="about" options={{ title: 'About' }} />
</Stack>
);
}
このスタックって何?
StackにStack.Screen入れてるね
スタックナビゲータは、アプリ内の異なる画面間を移動するための基盤です。Androidでは、スタックルートは現在の画面の上でアニメーションします。iOSでは、スタックルートは右からアニメーションします。Expo Routerには、新しいルートを追加するためのナビゲーション スタックを作成するStackコンポーネントが用意されています。
???
画面を積み重ねる、みたいな感じかな。
Stackコンポーネントを積み重ねていくのかな

import { Link } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
export default function Index() {
return (
<View style={styles.container}>
<Text style={styles.text}>Home screen</Text>
<Link href="/about" style={styles.button}>
リンクボタンだよ
</Link>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#25292e",
alignItems: "center",
justifyContent: "center",
},
text: {
color: "#fff",
},
button: {
fontSize: 20,
textDecorationLine: "underline",
color: "#fff",
},
});
↓

+not-found.tsxってファイルを
appの下に作っておくと存在しないページを開いちゃった時このファイルを表示してくれる。
これは必須だ!
import { Link, Stack } from "expo-router";
import { StyleSheet, View } from "react-native";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "お探しのページは見つかりませんでした" }} />
<View style={styles.container}>
<Link href="/" style={styles.button}>
トップページに戻る
</Link>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#25292e",
justifyContent: "center",
alignItems: "center",
},
button: {
fontSize: 20,
textDecorationLine: "underline",
color: "#fff",
},
});

tree app -L 2 -I "node_modules"
app
├── (tabs)
│ ├── _layout.tsx
│ ├── about.tsx
│ └── index.tsx
├── +not-found.tsx
└── _layout.tsx
app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: "Home" }} />
<Tabs.Screen name="about" options={{ title: "About" }} />
</Tabs>
);
}
app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
);
}
<Tabs>で タブができる
<Stack>はそのTabsを管理する
options={{ headerShown: false }}
をつけないと以下のような感じになる。

app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
import Ionicons from "@expo/vector-icons/Ionicons";
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: "#ffd33d",
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color, focused }) => <Ionicons name={focused ? "home-sharp" : "home-outline"} color={color} size={24} />,
}}
/>
<Tabs.Screen
name="about"
options={{
title: "About",
tabBarIcon: ({ color, focused }) => (
<Ionicons name={focused ? "information-circle" : "information-circle-outline"} color={color} size={24} />
),
}}
/>
</Tabs>
);
}
screenOptionsとかoptionでオシャレにできる。

画像を表示する
画像を表示するやつをインストール
✗ npx expo install expo-image
› Installing 1 SDK 53.0.0 compatible native module using npm
> npm install
added 1 package, and audited 942 packages in 910ms
174 packages are looking for funding
run `npm fund` for details
npm installとexpo installは何が違うんだろう
→ expo installでインストールすると現在のSDKのバージョンにあったライブラリをインストールすることができる。なので特に理由なければこれ使う

import { Image } from "expo-image";
import { StyleSheet, View } from "react-native";
const PlaceholderImage = require("@/assets/images/background-image.png");
export default function Index() {
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<Image source={PlaceholderImage} style={styles.image} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#25292e",
alignItems: "center",
},
imageContainer: {
flex: 1,
},
image: {
width: 320,
height: 440,
borderRadius: 18,
},
});
表示できた。ほんとwebっぽい

コンポーネントに分ける
components/ImageViewer.tsx
import { Image } from "expo-image";
import { ImageSourcePropType, StyleSheet } from "react-native";
type Props = {
imgSource: ImageSourcePropType;
};
export default function ImageViewer({ imgSource }: Props) {
return <Image source={imgSource} style={styles.image} />;
}
const styles = StyleSheet.create({
image: {
width: 320,
height: 440,
borderRadius: 18,
},
});
app/(tabs)/index.tsx
import { StyleSheet, View } from "react-native";
import ImageViewer from "@/components/ImageViewer";
const PlaceholderImage = require("@/assets/images/background-image.png");
export default function Index() {
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<ImageViewer imgSource={PlaceholderImage} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#25292e",
alignItems: "center",
},
imageContainer: {
flex: 1,
},
});
appの下にcomponentsなどのルーティングに関係ないディレクトリやファイルは作ってはならんらしい。
All screens/pages are files inside of app directory

ボタンつける
components/button.tsx
import { StyleSheet, View, Pressable, Text } from 'react-native';
type Props = {
label: string;
};
export default function Button({ label }: Props) {
return (
<View style={styles.buttonContainer}>
<Pressable style={styles.button} onPress={() => alert('You pressed a button.')}>
<Text style={styles.buttonLabel}>{label}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
buttonContainer: {
width: 320,
height: 68,
marginHorizontal: 20,
alignItems: 'center',
justifyContent: 'center',
padding: 3,
},
button: {
borderRadius: 10,
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
buttonLabel: {
color: '#fff',
fontSize: 16,
},
});
app/(tabs)/index.tsx
import { View, StyleSheet } from 'react-native';
import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';
const PlaceholderImage = require("@/assets/images/background-image.png");
export default function Index() {
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<ImageViewer imgSource={PlaceholderImage} />
</View>
<View style={styles.footerContainer}>
<Button label="Choose a photo" />
<Button label="Use this photo" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
},
imageContainer: {
flex: 1,
paddingTop: 28,
},
footerContainer: {
flex: 1 / 3,
alignItems: 'center',
},
});
なんか被ってるけどまあいいか。。笑
<Pressable>を使うのがおすすめらしい

ボタンコンポーネントにthemeの引数を追加してデザインを分けるようにする。
この時、コード変更後なぜかボタンにデザインが入らなくて困った。
Expoを再起動したら見栄えがチュートリアルと同じになったので、よくわからないこと起きたら
再起動試すのもありかも
Expo起動してるコンソールでr
押せばリロードできる
import { StyleSheet, View } from "react-native";
import Button from "@/components/button";
import ImageViewer from "@/components/ImageViewer";
const PlaceholderImage = require("@/assets/images/background-image.png");
export default function Index() {
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<ImageViewer imgSource={PlaceholderImage} />
</View>
<View style={styles.footerContainer}>
<Button theme="primary" label="Choose a photo" />
<Button label="Use this photo" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#25292e",
alignItems: "center",
},
imageContainer: {
flex: 1,
},
footerContainer: {
flex: 1 / 3,
alignItems: "center",
},
});
import FontAwesome from "@expo/vector-icons/FontAwesome";
import { Pressable, StyleSheet, Text, View } from "react-native";
type Props = {
label: string;
theme?: "primary";
};
export default function Button({ label, theme }: Props) {
if (theme === "primary") {
return (
<View style={[styles.buttonContainer, { borderWidth: 4, borderColor: "#ffd33d", borderRadius: 18 }]}>
<Pressable style={[styles.button, { backgroundColor: "#fff" }]} onPress={() => alert("You pressed a button.")}>
<FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} />
<Text style={[styles.buttonLabel, { color: "#25292e" }]}>{label}</Text>
</Pressable>
</View>
);
}
return (
<View style={styles.buttonContainer}>
<Pressable style={styles.button} onPress={() => alert("You pressed a button.")}>
<Text style={styles.buttonLabel}>{label}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
buttonContainer: {
width: 320,
height: 68,
marginHorizontal: 20,
alignItems: "center",
justifyContent: "center",
padding: 3,
},
button: {
borderRadius: 10,
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
flexDirection: "row",
},
buttonIcon: {
paddingRight: 8,
},
buttonLabel: {
color: "#fff",
fontSize: 16,
},
});

画像ピッカーのインスト
npx expo install expo-image-picker
※ 開発サーバー落としてからインストールせよとのこと(さっき落とさずインストールしたかも、そのせいでExpoの挙動がおかしかった説)

ボタンコンポーネントにonpress追加
type Props = {
label: string;
theme?: "primary";
onPress: () => void;
};
export default function Button({ label, theme, onPress }: Props) {
pickImageAsync関数追加
import { View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';
const PlaceholderImage = require('@/assets/images/background-image.png');
export default function Index() {
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (!result.canceled) {
console.log(result);
} else {
alert('You did not select any image.');
}
};
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<ImageViewer imgSource={PlaceholderImage} />
</View>
<View style={styles.footerContainer}>
<Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
<Button label="Use this photo" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
},
imageContainer: {
flex: 1,
},
footerContainer: {
flex: 1 / 3,
alignItems: 'center',
},
});
画像選択出せる、簡単!

あとは選択した画像のurlを保存するuseState作って、
それをImageViewrに渡す感じにして終わり
import { View, StyleSheet } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import Button from '@/components/Button';
import ImageViewer from '@/components/ImageViewer';
const PlaceholderImage = require('@/assets/images/background-image.png');
export default function Index() {
const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined);
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (!result.canceled) {
setSelectedImage(result.assets[0].uri);
} else {
alert('You did not select any image.');
}
};
return (
<View style={styles.container}>
<View style={styles.imageContainer}>
<ImageViewer imgSource={PlaceholderImage} selectedImage={selectedImage} />
</View>
<View style={styles.footerContainer}>
<Button theme="primary" label="Choose a photo" onPress={pickImageAsync} />
<Button label="Use this photo" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
},
imageContainer: {
flex: 1,
},
footerContainer: {
flex: 1 / 3,
alignItems: 'center',
},
});
import { ImageSourcePropType, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
type Props = {
imgSource: ImageSourcePropType;
selectedImage?: string;
};
export default function ImageViewer({ imgSource, selectedImage }: Props) {
const imageSource = selectedImage ? { uri: selectedImage } : imgSource;
return <Image source={imageSource} style={styles.image} />;
}
const styles = StyleSheet.create({
image: {
width: 320,
height: 440,
borderRadius: 18,
},
});