本文へ移動
バージョン: 6.x

カスタムナビゲーター

ナビゲーターを使用すると、アプリケーションのナビゲーション構造を定義できます。ナビゲーターは、ヘッダーやタブバーなどの共通要素もレンダリングし、これらを構成できます。

内部的には、ナビゲーターはプレーンなReactコンポーネントです。

組み込みナビゲーター

一般的に必要なナビゲーターをいくつか用意しています。

  • createStackNavigator - 一度に1つの画面をレンダリングし、画面間の遷移を提供します。新しい画面が開かれると、スタックの一番上に配置されます。
  • createDrawerNavigator - デフォルトでは画面の左側からスライドインするドロワーを提供します。
  • createBottomTabNavigator - ユーザーが複数の画面間を切り替えることができるタブバーをレンダリングします。
  • createMaterialTopTabNavigator - スワイプジェスチャーまたはタブバーを使用してユーザーが複数の画面間を切り替えることができるタブビューをレンダリングします。
  • createMaterialBottomTabNavigator - スワイプジェスチャーまたはタブバーを使用してユーザーが複数の画面間を切り替えることができるタブビューをレンダリングします。

カスタムナビゲーター構築用API

ナビゲーターは、ルーターとビューをバンドルし、ナビゲーション状態を受け取り、そのレンダリング方法を決定します。React Navigationの残りの部分と統合するカスタムナビゲーターを構築するために、useNavigationBuilderフックをエクスポートします。

useNavigationBuilder

このフックにより、コンポーネントはReact Navigationにフックできます。次の引数を受け入れます。

  • createRouter - ルーターオブジェクトを返すファクトリーメソッド(例:StackRouterTabRouter)。

  • options - フックとルーターのオプション。ナビゲーターはここにプロップを転送する必要があるため、ユーザーはナビゲーターを構成するためにプロップを提供できます。デフォルトでは、次のオプションが受け入れられます。

    • children(必須) - childrenプロップには、Screenコンポーネントとしてルート構成が含まれている必要があります。
    • screenOptions - screenOptionsプロップには、すべての画面のデフォルトオプションが含まれている必要があります。
    • initialRouteName - initialRouteNameプロップは、最初のレンダリング時にフォーカスする画面を決定します。このプロップはルーターに転送されます。

    ここに他のオプションが渡された場合、それらはルーターに転送されます。

このフックは、次のプロパティを持つオブジェクトを返します。

  • state - ナビゲーターのナビゲーション状態。コンポーネントはこの状態を受け取り、レンダリング方法を決定できます。

  • navigation - ナビゲーターがナビゲーション状態を操作するためのさまざまなヘルパーメソッドを含むナビゲーションオブジェクト。これは画面のナビゲーションオブジェクトとは異なり、画面にイベントを発行するためのemitなどのヘルパーが含まれています。

  • descriptors - ルートキーをプロパティとする、各ルートの記述子を含むオブジェクト。ルートの記述子には、descriptors[route.key]でアクセスできます。各記述子には、次のプロパティが含まれています。

    • navigation - 画面のナビゲーションプロップ。手動で画面に渡す必要はありません。ただし、ヘッダーコンポーネントなど、navigationプロップを受け取る必要がある画面外のコンポーネントをレンダリングする場合に便利です。
    • options - 指定されている場合、titleなどの画面のオプションを返すゲッター。
    • render - 実際の画面をレンダリングするために使用できる関数。descriptors[route.key].render()を呼び出すと、画面コンテンツを含むReact要素が返されます。画面をレンダリングするにはこのメソッドを使用することが重要です。そうしないと、子ナビゲーターがナビゲーションツリーに正しく接続されません。

import * as React from 'react';
import { Text, Pressable, View } from 'react-native';
import {
NavigationHelpersContext,
useNavigationBuilder,
TabRouter,
TabActions,
} from '@react-navigation/native';

function TabNavigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder(TabRouter, {
children,
screenOptions,
initialRouteName,
});

return (
<NavigationContent>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.map((route, index) => (
<Pressable
key={route.key}
onPress={() => {
const isFocused = state.index === index;
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});

if (!isFocused && !event.defaultPrevented) {
navigation.dispatch({
...TabActions.jumpTo(route.name, route.params),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title ?? route.name}</Text>
</Pressable>
))}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{state.routes.map((route, i) => {
return (
<View
key={route.key}
style={[
StyleSheet.absoluteFill,
{ display: i === state.index ? 'flex' : 'none' },
]}
>
{descriptors[route.key].render()}
</View>
);
})}
</View>
</NavigationContent>
);
}

ナビゲーターのnavigationオブジェクトには、子画面にカスタムイベントを発行するためのemitメソッドもあります。使い方は次のようになります。

navigation.emit({
type: 'transitionStart',
data: { blurring: false },
target: route.key,
});

dataは、eventオブジェクトのdataプロパティ(つまりevent.data)で使用できます。

targetプロパティは、イベントを受信する画面を決定します。targetプロパティが省略されている場合、イベントはナビゲーター内のすべての画面に送信されます。

createNavigatorFactory

このcreateNavigatorFactory関数は、NavigatorScreenのペアを作成する関数を作成するために使用されます。カスタムナビゲーターは、エクスポートする前にcreateNavigatorFactoryでナビゲーターコンポーネントをラップする必要があります。

import {
useNavigationBuilder,
createNavigatorFactory,
} from '@react-navigation/native';

// ...

export const createMyNavigator = createNavigatorFactory(TabNavigator);

次に、このように使用できます。

import { createMyNavigator } from './myNavigator';

const My = createMyNavigator();

function App() {
return (
<My.Navigator>
<My.Screen name="Home" component={HomeScreen} />
<My.Screen name="Feed" component={FeedScreen} />
</My.Navigator>
);
}

ナビゲーターの型チェック

ナビゲーターの型チェックを行うには、3つの型を提供する必要があります。

  • ビューで受け入れられるプロップの型
  • サポートされている画面オプションの型
  • ナビゲーターによって発行されるイベントタイプのマップ

たとえば、カスタムタブナビゲーターの型チェックを行うには、次のようなことができます。

import * as React from 'react';
import {
View,
Text,
Pressable,
StyleProp,
ViewStyle,
StyleSheet,
} from 'react-native';
import {
createNavigatorFactory,
DefaultNavigatorOptions,
ParamListBase,
CommonActions,
TabActionHelpers,
TabNavigationState,
TabRouter,
TabRouterOptions,
useNavigationBuilder,
} from '@react-navigation/native';

// Props accepted by the view
type TabNavigationConfig = {
tabBarStyle: StyleProp<ViewStyle>;
contentStyle: StyleProp<ViewStyle>;
};

// Supported screen options
type TabNavigationOptions = {
title?: string;
};

// Map of event name and the type of data (in event.data)
//
// canPreventDefault: true adds the defaultPrevented property to the
// emitted events.
type TabNavigationEventMap = {
tabPress: {
data: { isAlreadyFocused: boolean };
canPreventDefault: true;
};
};

// The props accepted by the component is a combination of 3 things
type Props = DefaultNavigatorOptions<
ParamListBase,
TabNavigationState<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
> &
TabRouterOptions &
TabNavigationConfig;

function TabNavigator({
initialRouteName,
children,
screenOptions,
tabBarStyle,
contentStyle,
}: Props) {
const { state, navigation, descriptors, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap
>(TabRouter, {
children,
screenOptions,
initialRouteName,
});

return (
<NavigationContent>
<View style={[{ flexDirection: 'row' }, tabBarStyle]}>
{state.routes.map((route, index) => (
<Pressable
key={route.key}
onPress={() => {
const isFocused = state.index === index;
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
data: {
isAlreadyFocused: isFocused,
},
});

if (!isFocused && !event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route),
target: state.key,
});
}
}}
style={{ flex: 1 }}
>
<Text>{descriptors[route.key].options.title ?? route.name}</Text>
</Pressable>
))}
</View>
<View style={[{ flex: 1 }, contentStyle]}>
{state.routes.map((route, i) => {
return (
<View
key={route.key}
style={[
StyleSheet.absoluteFill,
{ display: i === state.index ? 'flex' : 'none' },
]}
>
{descriptors[route.key].render()}
</View>
);
})}
</View>
</NavigationContent>
);
}

export default createNavigatorFactory<
TabNavigationState<ParamListBase>,
TabNavigationOptions,
TabNavigationEventMap,
typeof TabNavigator
>(TabNavigator);

ナビゲーターの拡張

すべての組み込みナビゲーターは、再利用して追加の機能を構築できるビューをエクスポートします。たとえば、ボトムタブナビゲーターを再構築するには、次のコードが必要です。

import * as React from 'react';
import {
useNavigationBuilder,
createNavigatorFactory,
TabRouter,
} from '@react-navigation/native';
import { BottomTabView } from '@react-navigation/bottom-tabs';

function BottomTabNavigator({
initialRouteName,
backBehavior,
children,
screenOptions,
...rest
}) {
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(TabRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});

return (
<NavigationContent>
<BottomTabView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
/>
</NavigationContent>
);
}

export default createNavigatorFactory(BottomTabNavigator);

これで、追加の機能を追加したり、動作を変更したりできます。たとえば、デフォルトのTabRouterの代わりにカスタムルーターを使用します。

import MyRouter from './MyRouter';

// ...

const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder(MyRouter, {
initialRouteName,
backBehavior,
children,
screenOptions,
});

// ...