Перейти к содержанию

Вложенные навигаторы

Вложенность навигаторов означает, что один навигатор отображается внутри экрана другого навигатора, например:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 также используется для экрана Home в стековом навигаторе внутри компонента App. Таким образом, здесь навигатор вкладок вложен внутрь стекового навигатора:

  • Stack.Navigator
    • Home (Tab.Navigator)
      • Feed (Screen)
      • Messages (Screen)
    • Profile (Screen)
    • Settings (Screen)

Вложенные навигаторы работают так же, как и вложенные обычные компоненты. Чтобы добиться желаемого поведения, часто необходимо вложить несколько навигаторов.

Как вложенные навигаторы влияют на поведение

При вложении навигаторов необходимо учитывать некоторые моменты:

Каждый навигатор хранит свою собственную историю навигации.

Например, при нажатии кнопки "Назад" на экране в навигаторе во вложенном стеке происходит возврат к предыдущему экрану внутри вложенного стека, даже если в качестве родителя используется другой навигатор.

Каждый навигатор имеет свои собственные опции.

Например, указание опции title в экране, вложенном в дочерний навигатор, не повлияет на заголовок, отображаемый в родительском навигаторе.

Если вы хотите добиться такого поведения, обратитесь к руководству параметры экрана с вложенными навигаторами. Это может быть полезно, если вы отображаете навигатор вкладок внутри стекового навигатора и хотите показывать заголовок активного экрана внутри навигатора вкладок в заголовке стекового навигатора.

Каждый экран в навигаторе имеет свои собственные параметры.

Например, все params, передаваемые экрану во вложенном навигаторе, находятся в свойстве route этого экрана и недоступны из экрана в родительском или дочернем навигаторе.

Если необходимо получить доступ к параметрам родительского экрана из дочернего экрана, можно использовать React Context для раскрытия параметров дочерних экранов.

Действия навигации обрабатываются текущим навигатором, а если не могут быть обработаны, то всплывают.

Например, если вы вызываете navigation.goBack() во вложенном экране, то возврат в родительский навигатор произойдет только в том случае, если вы уже находитесь на первом экране навигатора. Другие действия, такие как navigate, работают аналогично, т.е. навигация будет происходить во вложенном навигаторе, и если вложенный навигатор не справился с ней, то ее попытается выполнить родительский навигатор. В приведенном примере при вызове navigate('Messages') внутри экрана Feed навигатор вложенной вкладки обработает его, но если вызвать navigate('Settings'), то обработает родительский стековый навигатор.

Методы, специфичные для навигаторов, доступны для навигаторов, вложенных внутрь.

Например, если внутри навигатора ящика находится стек, то методы ящика openDrawer, closeDrawer, toggleDrawer и т. д. будут также доступны на реквизите navigation в навигаторе экрана внутри стека. Но если в качестве родителя ящика используется стековый навигатор, то экраны внутри стекового навигатора не будут иметь доступа к этим методам, поскольку они не вложены внутрь ящика.

Аналогично, если внутри stack navigator находится tab navigator, то экраны в tab navigator получат методы push и replace для stack в своем реквизите navigation.

Если необходимо диспетчеризировать действия для вложенных дочерних навигаторов от родительского, можно использовать navigation.dispatch:

1
navigation.dispatch(DrawerActions.toggleDrawer());

Вложенные навигаторы не получают события родительского навигатора

Например, если внутри навигатора вкладок вложен стековый навигатор, то при использовании navigation.addListener экраны в стековом навигаторе не будут получать события от родительского навигатора вкладок, такие как (tabPress).

Чтобы получать события от родительского навигатора, можно явно прослушивать события родителя с помощью функции navigation.getParent:

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

Здесь 'MyTabs' означает значение, переданное в свойстве id родительского Tab.Navigator, событие которого вы хотите прослушать.

Пользовательский интерфейс родительского навигатора отображается поверх пользовательского интерфейса дочернего навигатора

Например, если вложить навигатор stack в навигатор drawer, то можно увидеть, что drawer отображается над заголовком навигатора stack. Однако если вложить навигатор drawer внутрь stack, то drawer будет отображаться под заголовком stack. Это важный момент, который необходимо учитывать при выборе способа вложения навигаторов.

В своем приложении вы, вероятно, будете использовать эти шаблоны в зависимости от желаемого поведения:

  • Навигатор табов, вложенный внутрь начального экрана навигатора стека - новые экраны закрывают панель табов при нажатии на них.
  • Навигатор drawer, вложенный в начальный экран stack navigator со скрытым заголовком стека начального экрана - Drawer может быть открыт только с первого экрана стека.
  • Навигаторы стека, вложенные в каждый экран навигатора ящика - Ящик открывается поверх заголовка стека.
  • Навигаторы стека, вложенные в каждый экран навигатора табов - Панель табов всегда видна. Обычно при повторном нажатии на вкладку также открывается стек.

Переход к экрану во вложенном навигаторе

Рассмотрим следующий пример:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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>
    );
}

Здесь вы, возможно, захотите перейти на экран Root из компонента Feed:

1
navigation.navigate('Root');

Это работает, и на экране внутри компонента Root отображается начальный экран, которым является Home. Но иногда возникает необходимость управлять экраном, который должен быть показан при навигации. Для этого можно передать имя экрана в params:

1
navigation.navigate('Root', { screen: 'Profile' });

Теперь при навигации вместо экрана Home будет отображаться экран Profile.

Это может выглядеть совсем иначе, чем то, как навигация работала с вложенными экранами ранее. Разница заключается в том, что в предыдущих версиях вся конфигурация была статической, поэтому React Navigation могла статически находить список всех навигаторов и их экранов, обращаясь к вложенным конфигурациям.

В случае динамической конфигурации React Navigation не знает, какие экраны и где доступны, пока не отобразится навигатор, содержащий этот экран. Обычно экран не отображает свое содержимое до тех пор, пока вы не перейдете к нему, поэтому конфигурация навигаторов, которые еще не отобразились, недоступна. Поэтому необходимо указывать иерархию, на которую осуществляется навигация. Именно поэтому для упрощения кода следует использовать как можно меньше вложенных навигаторов.

Передача параметров экрану во вложенном навигаторе

Передавать параметры можно также, указав ключ params:

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

Если навигатор уже был отрисован, то при переходе к другому экрану в случае стекового навигатора будет отрисован новый экран.

Аналогичный подход можно использовать и для глубоко вложенных экранов. Обратите внимание, что вторым аргументом navigate здесь является просто params, поэтому вы можете сделать что-то вроде:

1
2
3
4
5
6
7
8
9
navigation.navigate('Root', {
    screen: 'Settings',
    params: {
        screen: 'Sound',
        params: {
            screen: 'Media',
        },
    },
});

В приведенном выше случае вы переходите к экрану Media, который находится в навигаторе, вложенном в экран Sound, который находится в навигаторе, вложенном в экран Settings.

Рендеринг начального маршрута, заданного в навигаторе

По умолчанию при переходе по экрану во вложенном навигаторе в качестве начального экрана используется указанный экран, а начальный маршрут, заданный в навигаторе, игнорируется. Такое поведение отличается от поведения React Navigation 4.

Если вам необходимо отображать начальный маршрут, указанный в навигаторе, вы можете отключить поведение использования указанного экрана в качестве начального, задав initial: false:

1
2
3
4
navigation.navigate('Root', {
    screen: 'Settings',
    initial: false,
});

Это влияет на то, что происходит при нажатии кнопки назад. Если есть начальный экран, то кнопка "Назад" приведет пользователя туда.

Вложение нескольких навигаторов

Иногда бывает полезно вложить несколько навигаторов, таких как stack, drawer или tabs.

При вложении нескольких навигаторов в виде стека, ящика или нижней вкладки будут показаны заголовки как дочернего, так и родительского навигаторов. Однако обычно более желательно показывать заголовок в дочернем навигаторе и скрывать его на экране родительского навигатора.

Для этого можно скрыть заголовок в экране, содержащем навигатор, с помощью опции headerShown: false.

Например:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 во всех навигаторах:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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>
    );
}

Лучшие практики при вложении

Мы рекомендуем свести вложенность навигаторов к минимуму. Постарайтесь добиться желаемого поведения при минимальной вложенности. Вложенность имеет множество недостатков:

  • Она приводит к созданию глубоко вложенной иерархии представлений, что может вызвать проблемы с памятью и производительностью на младших моделях устройств.
  • Вложение однотипных навигаторов (например, вкладки внутри вкладок, ящик внутри ящика и т.д.) может привести к запутанному пользовательскому интерфейсу.
  • При чрезмерной вложенности код становится сложным для понимания при переходе к вложенным экранам, настройке глубоких ссылок и т.д.

Рассматривайте вложенные навигаторы как способ достижения желаемого пользовательского интерфейса, а не как способ организации кода. Если необходимо создать отдельную группу экранов для организации, то вместо отдельных навигаторов можно использовать компонент Group.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<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>

Ссылки

Комментарии