React Native + Expo で開発を始めて画面遷移させるまで

42729 ワード

Expo のインストール

npm i -g expo-cli

React Native のプロジェクトを作成

expo init my-first-react-native-app

✔ Choose a template: › blank (TypeScript)  same as blank but with TypeScript configuration
- cd my-first-react-native-app
- yarn start # you can open iOS, Android, or web from here, or run them directly with the commands below.
- yarn android
- yarn ios
- yarn web

ビルドサーバーを起動してアプリを動かす

npm start

Developer tools running on http://localhost:19002 というコメントが表示された後、http://localhost:19002 をブラウザで開く。

iOS のシミュレータを動かそうとして、以下のようなエラーが出た場合。

› Opening on iOS...
xcrun exited with non-zero code: 2
An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=2):
Unable to boot device because we cannot determine the runtime bundle.
No such file or directory

下記の対応を行い、npm start → キーボードの i を押下 で解消できる。

xcode を立ち上げ -> Xcode メニュー -> Open Developer Tool -> Simulator  でシミュレータを立ち上げたら、シミュレータメニューの Device -> Erase all content and settings でセッティングをクリア。
xcode をアップデートしたら expo で iOS シミュレータが起動できなくなった

React Native Paper をセットアップ

React Native 向けに開発された UI ライブラリで、Material Design に準拠している。

npm i react-native-paper

ルートディレクトリにある babel.config.js を以下のように修正する。

babel.config.js
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    env: {
      production: {
        plugins: ['react-native-paper/babel']
      }
    }
  };
};

サンプルを動かす

ここまで作ってきた環境を使って、以下のような画面を動かしてみる。

作るのは以下の 3 つ。

  • App.tsx
  • lib/components/Main.tsx
  • lib/components/MyCard.tsx
App.tsx
import { Provider as PaperProvider } from 'react-native-paper'
import Main from './lib/components/Main'

export default function App() {
  return (
    <PaperProvider>
      <Main />
    </PaperProvider>
  )
}
lib/components/Main.tsx
import React from 'react'
import { Button, Headline, Title } from 'react-native-paper'
import { SafeAreaView, ScrollView } from 'react-native'
import MyCard from './MyCard'

const Main = () => (
  <SafeAreaView>
    <ScrollView>
      <Headline
        style={{
          margin: 15,
        }}
      >
        色んなパーツをごちゃまぜ
      </Headline>
      <Title
        style={{
          margin: 15,
        }}
      >
        タイトルだよ〜
      </Title>
      <Button
        mode={'contained'}
        style={{
          margin: 30,
        }}
      >
        containedなボタン
      </Button>
      <MyCard />
    </ScrollView>
  </SafeAreaView>
)

export default Main
lib/components/MyCard.tsx
import * as React from 'react'
import { Avatar, Button, Card, Title, Paragraph } from 'react-native-paper'
import { ReactNode } from 'react'

const LeftContent = (props: ReactNode) => <Avatar.Icon {...props} icon="folder" />

const MyCard = () => (
  <Card
    style={{
      margin: 20,
    }}
  >
    <Card.Title title="Card Title" subtitle="Card Subtitle" left={LeftContent} />
    <Card.Content>
      <Title>Card title</Title>
      <Paragraph>Card content</Paragraph>
    </Card.Content>
    <Card.Cover source={{ uri: 'https://picsum.photos/700' }} />
    <Card.Actions>
      <Button>Cancel</Button>
      <Button>Ok</Button>
    </Card.Actions>
  </Card>
)

export default MyCard

ここまで触ってみて、React + SwiftUI = React Native のような印象を受けた。

React と SwiftUI 両方の経験があるとキャッチアップしやすいように思えた。

画面遷移を実装する

ベースとなるライブラリのインストール。

npm i @react-navigation/native
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

左右にスライドして画面を切り替えるパターン(Stack Navigator)

ボタンクリックで 右 → ← 左 と画面が切り替わっていく画面の作り方。

npm i @react-navigation/stack

初期画面

ボタンクリックで移動する。

Disconnected from Metro は気にしないでください。

コードをいじるのは以下の 3 ファイル。

  • App.tsx
  • lib/components/HomeScreen.tsx
  • lib/components/DetailScreen.tsx
App.tsx
import 'react-native-gesture-handler'
import { NavigationContainer } from '@react-navigation/native'
import HomeScreen from './lib/components/HomeScreen'
import { createStackNavigator } from '@react-navigation/stack'
import DetailScreen from './lib/components/DetailScreen'

export type RootStackParamList = {
  Home: undefined
  Detail: undefined
}

const Stack = createStackNavigator()
export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName={'Home'}>
        <Stack.Screen name={'Home'} component={HomeScreen} />
        <Stack.Screen name={'Detail'} component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}
lib/components/HomeScreen.tsx
import * as React from 'react'
import { Text, View, Button } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { RootStackParamList } from '../../App'

type homeScreenProp = StackNavigationProp<RootStackParamList, 'Home'>
const HomeScreen = () => {
  const navigation = useNavigation<homeScreenProp>()
  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Text>ホーム画面</Text>
      <Button title={'詳細へ'} onPress={() => navigation.navigate('Detail')} />
    </View>
  )
}

export default HomeScreen

lib/components/DetailScreen.tsx
import * as React from 'react'
import { View, Text, Button } from 'react-native'
import { useNavigation } from '@react-navigation/native'

const DetailScreen = () => {
  const navigation = useNavigation()
  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <Text>詳細画面</Text>
      <Button title={'ホームへ'} onPress={() => navigation.goBack()} />
    </View>
  )
}

export default DetailScreen

参考

Hello React Navigation

エラー対応

以下のようなnavigation.navigate で...

<Button title={"詳細へ"} onPress={() => navigation.navigate("Detail")} />

以下のようなエラーが出た場合

TS2345: Argument of type 'string' is not assignable to parameter of type '{ key: string; params?: undefined; merge?: boolean | undefined; } | { name: never; key?: string | undefined; params: never; merge?: boolean | undefined; }'.

こちらの StackOverflow を参考に解消した。

navigation.navigate('Home') showing some error in typescript

タブで切り替えるパターン(Tab Navigator)

npm i @react-navigation/bottom-tabs

修正は App.tsx のみ。

App.tsx
import 'react-native-gesture-handler'
import { NavigationContainer } from '@react-navigation/native'
import HomeScreen from './lib/components/HomeScreen'
+ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import DetailScreen from './lib/components/DetailScreen'

export type RootStackParamList = {
  Home: undefined
  Detail: undefined
}

+ const Tab = createBottomTabNavigator()
export default function App() {
  return (
    <NavigationContainer>
+      <Tab.Navigator initialRouteName={'Home'}>
+        <Tab.Screen name={'Home'} component={HomeScreen} />
+        <Tab.Screen name={'Detail'} component={DetailScreen} />
+      </Tab.Navigator>
    </NavigationContainer>
  )
}