跳至主要內容
版本:6.x

巢狀導覽器

巢狀導覽器表示在另一個導覽器的畫面中呈現導覽器,例如

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 元件包含分頁導覽器。App 元件中,Home 元件也會用於堆疊導覽器中的 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'),父堆疊導覽器將會處理它。

舉例來說,如果您在抽屜導覽器中疊放,抽屜的 openDrawercloseDrawertoggleDrawer 方法等,也會在疊放導覽器中螢幕的 navigation prop 中使用。但假設您有疊放導覽器為抽屜的父層,則疊放導覽器中的螢幕無法存取這些方法,因為它們並未嵌套於抽屜中。

類似地,如果您在疊放導覽器中使用標籤導覽器,標籤導覽器中的螢幕將在其 navigation prop 中取得疊放的 pushreplace 方法。

如果您需要從父層分派動作至嵌套的子導覽器,您可以使用 navigation.dispatch

navigation.dispatch(DrawerActions.toggleDrawer());

嵌套導覽器不會收到父層事件

例如,如果您在標籤導覽器中嵌套疊放導覽器,則當使用 navigation.addListener 時,疊放導覽器中的螢幕不會收到由母標籤導覽器發出的事件,例如 (tabPress)。

要接收來自父導覽器的事件,您可以明確使用 navigation.getParent 偵聽父事件

const unsubscribe = navigation
.getParent('MyTabs')
.addListener('tabPress', (e) => {
// Do something
});

此處的 'MyTabs' 指的是您在您要偵聽其事件的父層 Tab.Navigatorid 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' });

現在,瀏覽後會顯示Profile畫面,而非Home

此方式看起來可能跟先前使用巢狀畫面而運作的導覽方式有相當大的不同。差異在於舊版中,所有設定都是靜態的,因此 React 導覽可以遞迴巢狀設定來動態找出所有導覽器及其畫面的清單。但對於動態設定,React 導覽在包含畫面的導覽器顯示之前,並不知道有哪些畫面可用以及這些畫面在那裡。一般來說,畫面並不會顯示其內容,直到您瀏覽到該畫面為止,因此尚未顯示的導覽器設定並不會提供。這使得有必要指定您要瀏覽的階層。這是您應盡量減少導覽器巢狀的原因之一,以確保您的程式碼簡潔。

傳遞參數給巢狀導覽器中的畫面

您也可以透過指定params鍵來傳遞參數

navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});

如果導覽器已經顯示,瀏覽另一個畫面時,堆疊導覽器會推入一個新畫面。

對於深入巢狀的畫面,您可以遵循類似的方式。請注意,navigate的第二個參數在此僅為params,因此您可以採取以下類似動作

navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});

在上述範例中,您瀏覽至Media畫面,該畫面位於巢狀在Sound畫面中的導覽器內,而Sound畫面又位於巢狀在Settings畫面中的導覽器內。

顯示導覽器中定義的初始路徑

預設情況下,當您在巢狀導覽器中瀏覽畫面時,指定畫面會用作初始畫面,且導覽器中的初始路徑屬性能會被忽略。此行為與 React 導覽 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>