React Navigationを使ってみる

# はじめに

react navigationを使うならreact nativeは使わない手はないと感じたので学んで行こうかなと思います．
我流でやっていくとよくわからない罠にハマることが多かった過去があるので，公式のやり方を学んでいこうかと思います．

# 動作環境

ProductName:	macOS
ProductVersion:	11.7.2
BuildVersion:	20G1020

expo: 6.1.0

"@react-navigation/bottom-tabs": "^6.0.5",
"@react-navigation/native": "^6.0.2",
"@react-navigation/native-stack": "^6.1.0",

# 参考にしたサイト

[react navigation公式](https://reactnavigation.org)
[expo のtabテンプレート](https://github.com/expo/expo/tree/main/templates/expo-template-tabs)
[【React Native入門】React Navigation(画面移動)の設定方法を理解する](https://reffect.co.jp/react/react-navigation#Native_Stack_Navigator)

# やることまとめ

## 全体
パッケージのインストール
```
npx expo install @react-navigation/native
```

## Stack
パッケージのインストール
```
npx expo install @react-navigation/native-stack
```

``` ts: app.tsx
export type RootStackParamList = {
    Stack1: { Stack1Moji: string };
    Stack2: undefined;
};

export type RootStackScreenProps<Screen extends keyof RootStackParamList> =
    NativeStackScreenProps<RootStackParamList, Screen>;

const Stack = createNativeStackNavigator<RootStackParamList>();
function Stacks() {
    return (
        <Stack.Navigator>
            <Stack.Screen
                name="Stack1"
                component={Stack1Screen}
                options={({ route }) => ({
                    title: route.params
                        ? "Stack1-" + route.params.Stack1Moji
                        : "Stack1-undifine",
                })}
            />
            <Stack.Group screenOptions={{ presentation: "modal" }}>
                <Stack.Screen name="Stack2" component={Stack2Screen} />
            </Stack.Group>
        </Stack.Navigator>
    );
}

function Stack1Screen({ route, navigation }: RootStackScreenProps<"Stack1">) {
    return (
        <View
            style={{ flex: 1, alignItems: "center", justifyContent: "center" }}
        >
            <Text>Stack1Screen</Text>
            <Text>moji: {route.params?.Stack1Moji}</Text>
            <Button
                title="Go Stack2"
                onPress={() => navigation.navigate("Stack2")}
            />
        </View>
    );
}

function Stack2Screen({ navigation }: RootStackScreenProps<"Stack2">) {
    const [enterdMoji, setEnterdMoji] = React.useState("");
    return (
        <View
            style={{ flex: 1, alignItems: "center", justifyContent: "center" }}
        >
            <Text>Stack2Screen</Text>
            <Button title="Go back" onPress={() => navigation.goBack()} />
            <TextInput
                multiline
                placeholder="What's on your mind?"
                style={{ height: 200, padding: 10, backgroundColor: "white" }}
                value={enterdMoji}
                onChangeText={setEnterdMoji}
            />
            <Button
                title="Done"
                onPress={() => {
                    navigation.navigate("Stack1", { Stack1Moji: enterdMoji });
                }}
            />
        </View>
    );
}
```

## Tab
パッケージのインストール
```
npx expo install @react-navigation/bottom-tabs
```
``` ts; app.tsx
export type RootTabParamList = {
    Tab1: undefined;
    Tab2: { tab2Moji: string | null };
};

export type RootTabScreenProps<Screen extends keyof RootTabParamList> =
    CompositeScreenProps<
        BottomTabScreenProps<RootTabParamList, Screen>,
        NativeStackScreenProps<RootStackParamList>
    >;

const Tab = createBottomTabNavigator<RootTabParamList>();

function Tabs() {
    return (
        <Tab.Navigator>
            <Tab.Screen name="Tab1" component={Tab1Screen} />
            <Tab.Screen name="Tab2" component={Stacks} />
        </Tab.Navigator>
    );
}
```

## drawer
```
## ドローワー使う時に必要
npx expo install @react-navigation/drawer react-native-reanimated
## ドローワーを使うのにbabel.config.jsを修正
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'],
  };
};
## クリアも忘れずに
npx expo start --clear
``` 
``` ts: app.tsx
export type RootDrawerParamList = {
    Drawer1: undefined;
    Drawer2: { Drawer2Moji: string | null };
};

const Drawer = createDrawerNavigator<RootDrawerParamList>();

function Drawers() {
    return (
        <Drawer.Navigator>
            <Drawer.Screen name="Drawer1" component={TemplateScreen} />
            <Drawer.Screen name="Drawer2" component={Tabs} />
        </Drawer.Navigator>
    );
}
```


1

# 試行錯誤

## 画面遷移

参考サイト：[Moving between screens](https://reactnavigation.org/docs/navigating)

この部分で詰まった
エラー内容
`バインド要素 'navigation' には暗黙的に 'any' 型が含まれます。ts(7031)`
```
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
```
引数のnavigationの型がちゃんとしてないんだろう．TypeScriptだし．それ以降のことはわからん・・・

解決するのにお世話になったサイト
- [React NavigationをTypescriptと一緒に使う際につまづいたところ
](https://crieit.net/posts/React-Navigation-Typescript)
- [React Navigation](https://reactnavigation.org/docs/typescript/)

修正した箇所は
1. RootStackParamListを作成する
今回はtype.tsxファイルを別途作成し以下のコードを追記．
別ファイルなのでexportもやっておく．
```
export type RootStackParamList ={
    Home: undefined;
    Deteals: undefined;
}
```
1. NavigatorにRootStackParamListを登録する
```
const HomeStack = createNativeStackNavigator<RootStackParamList>();
```
1. Stack.Screenを使用していた箇所をHomeStackに変更する
```
      <Stack.Navigator initialRouteName="Home">
        <HomeStack.Screen name="Home" component={HomeScreen} />
        <HomeStack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>

```
1. Screenにnavigationの追加
```
    const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, "Home">>();
```
この"Home"ってなんぞや？
自信のスクリーン名なのか？親のスクリーン名なのか？

## 変数を渡す

この部分で怒られた．
Detailsの引数が指定されていないのでしょう．
```
navigation.navigate("Details", {
    itemId: 86,
    otherParam: "anything you want here",
});
```

[ここに対処法がありました．](https://reactnavigation.org/docs/typescript/)
RootStackのDetailsに型宣言します．
```
export type RootStackParamList = {
    Home: undefined;
    Details: { itemId: number; otherParam: string };
};
```
DetailsScreenに引数を指定して型宣言をする．
パラメータはroute.paramsで取得する．
```
function DetailsScreen({ route, navigation }: DetailsProps) {
    const { itemId, otherParam } = route.params;
```

## ヘッダーの編集
[Configuring the header bar](https://reactnavigation.org/docs/headers)

画面遷移時にヘッダーを指定するためには
ProfileスクリーンにHomeスクリーンから移動するためのボタンを追加．
この時に引数にパラメータを渡す
```
<Button
    title="Go to Profile"
    onPress={() =>
        navigation.navigate("Profile", {
            name: "Home -> Profile",
        })
    }
/>
```

Profileスクリーン情報をナビゲータに登録する

```
<HomeStack.Screen
    name="Profile"
    component={ProfileScreen}
    options={({ route }) => ({ title: route.params.name })}
/>
```

TypeScriptだと引数が判定できないということで
```
export type RootStackParamList = {
    Home: { post: string };
    CreatePost: undefined;
    Details: { itemId: number; otherParam: string };
    Profile: { name: string };
};
```
と
```
export type ProfileProps = NativeStackScreenProps<
    RootStackParamList,
    "Profile"
>;
```
を追加

自身のスクリーンのタイトルを変更するにはProfileScreenでnavigation.setOptionsを使用して変更する
```
<Button
    title="Update the title"
    onPress={() => navigation.setOptions({ title: "Updated!" })}
/>
```

## drawerが起動できない

EXPO SDK　のバージョンが47.0.xにアップデートしたらdrawerが使えなくなっていた．

[こちらのサイトを参考にしてプロジェクトを作成して](https://zenn.dev/takanori_is/articles/build-slide-menu-with-expo-and-react-navigation)
[こちらのサイトのbabel設定したら動いた](https://docs.expo.dev/versions/latest/sdk/reanimated/#web-support)
```
module.exports = function (api) {
    api.cache(true);
    return {
        presets: ["babel-preset-expo"],
        plugins: ["react-native-reanimated/plugin"],
    };
};
```
babel.config.jsを↑に変更して
```
npx expo start --clear
```
を実行
わかんねぇかったよ・・迂闊にアップデートするもんじゃ無い