ネストされたナビゲーター
ネストされたナビゲーターとは、あるナビゲーターの画面内に別のナビゲーターをレンダリングすることを意味します。例えば、
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
上記の例では、Home
コンポーネントにタブナビゲーターが含まれています。 Home
コンポーネントは、App
コンポーネント内のスタックナビゲーターのHome
画面にも使用されます。つまり、ここではタブナビゲーターがスタックナビゲーター内にネストされています。
Stack.Navigator
Home
(Tab.Navigator
)Feed
(Screen
)Messages
(Screen
)
Profile
(Screen
)Settings
(Screen
)
ネストされたナビゲーターは、通常のコンポーネントのネストと非常によく似た動作をします。目的の動作を実現するには、複数のナビゲーターをネストする必要があることがよくあります。
ネストされたナビゲーターが動作に与える影響
ナビゲーターをネストする際には、いくつかの注意点があります。
各ナビゲーターは独自のナビゲーション履歴を保持します
たとえば、ネストされたスタックナビゲーター内の画面で戻るボタンを押すと、親として別のナビゲーターが存在する場合でも、ネストされたスタック内の前の画面に戻ります。
各ナビゲーターには独自のオプションがあります
たとえば、子ナビゲーターにネストされた画面でtitle
オプションを指定しても、親ナビゲーターに表示されるタイトルには影響しません。
この動作を実現するには、ネストされたナビゲーターの画面オプションのガイドを参照してください。これは、スタックナビゲーター内にタブナビゲーターをレンダリングし、スタックナビゲーターのヘッダーにタブナビゲーター内のアクティブな画面のタイトルを表示する場合に役立ちます。
ナビゲーターの各画面には独自のパラメータがあります
たとえば、ネストされたナビゲーターの画面に渡されたparams
は、その画面のroute
propにあり、親または子ナビゲーターの画面からはアクセスできません。
子画面から親画面のパラメータにアクセスする必要がある場合は、React Contextを使用してパラメータを子に公開できます。
ナビゲーションアクションは現在のナビゲーターによって処理され、処理できない場合はバブルアップします
たとえば、ネストされた画面でnavigation.goBack()
を呼び出すと、ナビゲーターの最初の画面に既にいる場合にのみ、親ナビゲーターに戻ります。 navigate
などの他のアクションも同様に機能します。つまり、ナビゲーションはネストされたナビゲーターで行われ、ネストされたナビゲーターが処理できない場合は、親ナビゲーターが処理を試みます。上記の例では、Feed
画面内でnavigate('Messages')
を呼び出すと、ネストされたタブナビゲーターが処理しますが、navigate('Settings')
を呼び出すと、親スタックナビゲーターが処理します。
ナビゲーター固有のメソッドは、ネストされたナビゲーター内で使用できます
たとえば、ドロワーナビゲーター内にスタックがある場合、ドロワーのopenDrawer
、closeDrawer
、toggleDrawer
メソッドなどは、スタックナビゲーター内の画面のnavigation
propでも使用できます。ただし、ドロワーの親としてスタックナビゲーターがある場合、スタックナビゲーター内の画面はこれらのメソッドにアクセスできません。これは、ドロワー内にネストされていないためです。
同様に、スタックナビゲーター内にタブナビゲーターがある場合、タブナビゲーターの画面は、navigation
propにスタックのpush
およびreplace
メソッドを取得します。
親からネストされた子ナビゲーターにアクションをディスパッチする必要がある場合は、navigation.dispatch
を使用できます。
navigation.dispatch(DrawerActions.toggleDrawer());
ネストされたナビゲーターは親のイベントを受信しません
たとえば、タブナビゲーター内にスタックナビゲーターがネストされている場合、スタックナビゲーターの画面は、navigation.addListener
を使用する場合、親タブナビゲーターによって発行されたイベント(tabPress
など)を受信しません。
親ナビゲーターからイベントを受信するには、navigation.getParent
を使用して親のイベントを明示的にリッスンできます。
const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
});
ここで、'MyTabs'
は、イベントをリッスンする親Tab.Navigator
のid
propに渡す値を指します。
親ナビゲーターのUIは子ナビゲーターの上にレンダリングされます
たとえば、スタックナビゲーターをドロワーナビゲーター内にネストすると、ドロワーがスタックナビゲーターのヘッダーの上に表示されます。ただし、ドロワーナビゲーターをスタック内にネストすると、ドロワーはスタックのヘッダーの下に表示されます。これは、ナビゲーターのネスト方法を決定する際に考慮すべき重要なポイントです。
アプリでは、目的の動作に応じて、おそらくこれらのパターンを使用します。
- スタックナビゲーターの最初の画面内にネストされたタブナビゲーター - 新しい画面をプッシュすると、タブバーが覆われます。
- スタックナビゲーターの最初の画面内にネストされたドロワーナビゲーター(最初の画面のスタックヘッダーは非表示) - ドロワーはスタックの最初の画面からのみ開くことができます。
- ドロワーナビゲーターの各画面内にネストされたスタックナビゲーター - ドロワーはスタックのヘッダーの上に表示されます。
- タブナビゲーターの各画面内にネストされたスタックナビゲーター - タブバーは常に表示されます。通常、タブをもう一度押すと、スタックも一番上にポップされます。
ネストされたナビゲーター内の画面への移動
次の例を考えてみましょう。
function Root() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Drawer.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Root"
component={Root}
options={{ headerShown: false }}
/>
<Stack.Screen name="Feed" component={Feed} />
</Stack.Navigator>
</NavigationContainer>
);
}
ここで、Feed
コンポーネントからRoot
画面に移動したい場合があります。
navigation.navigate('Root');
これは機能し、Root
コンポーネント内の最初の画面(Home
)が表示されます。ただし、ナビゲーション時に表示される画面を制御したい場合があります。これを実現するには、パラメータに画面の名前を渡すことができます。
navigation.navigate('Root', { screen: 'Profile' });
これで、ナビゲーション時にHome
の代わりにProfile
画面がレンダリングされます。
これは、以前のネストされた画面でのナビゲーションの動作とは大きく異なるように見える場合があります。違いは、以前のバージョンではすべての構成が静的であったため、React Navigationはネストされた構成を再帰的に調べて、すべてのナビゲーターとその画面のリストを静的に見つけることができたことです。ただし、動的構成では、React Navigationは、画面を含むナビゲーターがレンダリングされるまで、どの画面がどこで使用可能かわかりません。通常、画面に移動するまで画面の内容はレンダリングされないため、レンダリングされていないナビゲーターの構成はまだ使用できません。このため、移動先の階層を指定する必要があります。これも、コードをシンプルにするために、ナビゲーターのネストをできるだけ少なくする必要がある理由です。
ネストされたナビゲーター内の画面へのパラメータ渡し
params
キーを指定することで、パラメータを渡すこともできます。
navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});
ナビゲーターが既にレンダリングされている場合、別の画面に移動すると、スタックナビゲーターの場合、新しい画面がプッシュされます。
深くネストされた画面でも同様の方法に従うことができます。ここで、navigate
の2番目の引数は単なるparams
であるため、次のようなことができます。
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
上記の場合、Settings
画面にネストされたナビゲーター内にあるSound
画面にネストされたナビゲーター内にあるMedia
画面に移動しています。
ナビゲーターで定義された初期ルートのレンダリング
デフォルトでは、ネストされたナビゲーター内の画面に移動すると、指定された画面が初期画面として使用され、ナビゲーターの初期ルートpropは無視されます。この動作は、React Navigation 4とは異なります。
ナビゲーターで指定された初期ルートをレンダリングする必要がある場合は、initial: false
を設定することで、指定された画面を初期画面として使用する動作を無効にできます。
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
これは、戻るボタンを押したときの動作に影響します。初期画面がある場合、戻るボタンはユーザーをそこに連れて行きます。
複数ナビゲーターのネスト
スタック、ドロワー、タブなどの複数のナビゲーターをネストすると便利な場合があります。
複数のスタック、ドロワー、またはボトムタブナビゲーターをネストする場合、子と親の両方のナビゲーターからヘッダーが表示されます。ただし、通常は子のナビゲーターにヘッダーを表示し、親ナビゲーターの画面ではヘッダーを非表示にする方が望ましいです。
これを実現するには、headerShown: false
オプションを使用して、ナビゲーターを含む画面でヘッダーを非表示にします。
例えば
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}
これらの例では、ボトムタブナビゲーターを別のスタックナビゲーター内に直接ネストしていますが、スタックナビゲーターがタブナビゲーター内にあり、それが別のスタックナビゲーター内にある場合、スタックナビゲーターがドロワーナビゲーター内にある場合など、他のナビゲーターが間にある場合でも同じ原則が適用されます。
いずれのナビゲーターにもヘッダーを表示したくない場合は、すべてのナビゲーターで headerShown: false
を指定できます。
function Home() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}
ネスト時のベストプラクティス
ナビゲーターのネストは最小限にすることをお勧めします。可能な限り少ないネストで目的の動作を実現するようにしてください。ネストには多くの欠点があります。
- 深くネストされたビュー階層になり、低性能デバイスでメモリとパフォーマンスの問題が発生する可能性があります。
- 同じタイプのナビゲーター(例:タブ内のタブ、ドロワー内のドロワーなど)をネストすると、UXが混乱する可能性があります。
- 過度にネストすると、ネストされた画面への移動、ディープリンクの設定などが複雑になり、コードが理解しにくくなります。
ナビゲーターのネストは、コードを整理する方法ではなく、目的のUIを実現する方法と考えてください。整理のために画面を別々のグループに分けたい場合は、別のナビゲーターを使用する代わりに、Group
コンポーネントを使用できます。
<Stack.Navigator>
{isLoggedIn ? (
// Screens for logged in users
<Stack.Group>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Group>
) : (
// Auth screens
<Stack.Group screenOptions={{ headerShown: false }}>
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
</Stack.Group>
)}
{/* Common modal screens */}
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Help" component={Help} />
<Stack.Screen name="Invite" component={Invite} />
</Stack.Group>
</Stack.Navigator>