Перейти к содержимому

Лекция 5. Навигация в мобильных приложениях (React Navigation)

Введение

Любое нетривиальное мобильное приложение состоит из множества экранов: список товаров и карточка товара, лента и профиль, вход и регистрация, настройки. Пользователь постоянно перемещается между ними: открывает детали, возвращается назад, переключается между разделами через нижние вкладки, открывает боковое меню. Всё это — навигация.

В вебе за переходы отвечает сам браузер: есть адресная строка, кнопка «Назад», история. В мобильном приложении ничего этого «из коробки» нет — стек экранов, анимации переходов, аппаратную кнопку «Назад» на Android, заголовки с кнопкой возврата нужно реализовывать самостоятельно. Чтобы не изобретать это каждый раз, в экосистеме React Native используют библиотеку React Navigation — де-факто стандарт навигации.

В этой лекции разберём модель экранов и навигаторов, установку и настройку React Navigation, три основных типа навигаторов (Stack, Tab, Drawer), передачу параметров между экранами, вложенные навигаторы и базовую типобезопасность маршрутов с TypeScript.


1. Зачем нужна навигация: модель экранов и навигаторов

Навигация решает несколько задач одновременно:

  • Переходы между экранами — открыть новый экран, вернуться назад.
  • Хранение истории — пользователь должен иметь возможность вернуться туда, откуда пришёл.
  • Анимации и жесты — слайд вперёд/назад, свайп для возврата, что соответствует ожиданиям пользователей iOS и Android.
  • Системная интеграция — аппаратная кнопка «Назад» на Android, заголовки, deep links.
  • Передача данных — какой именно товар открыть, какой id передать на следующий экран.

Ключевые понятия в React Navigation:

  • Экран (Screen) — обычный React-компонент, который показывается пользователю. Например, HomeScreen, DetailsScreen.
  • Навигатор (Navigator) — компонент, который управляет группой экранов: хранит, какой экран активен, ведёт историю, рисует заголовки и переходы. Каждый навигатор задаёт свою модель перемещения (стек, вкладки, меню).
  • NavigationContainer — корневой контейнер, который оборачивает всё дерево навигаторов и связывает его с состоянием приложения и системой.
  • Объект navigation — передаётся в каждый экран и позволяет инициировать переходы (navigate, goBack, push и т. д.).
  • Объект route — описывает текущий маршрут: его имя (route.name) и переданные параметры (route.params).

Важно понимать: навигаторы можно вкладывать друг в друга. Реальные приложения почти всегда комбинируют несколько типов — например, нижние вкладки (Tab), внутри каждой из которых живёт свой стек (Stack).


2. Установка и базовая настройка

React Navigation состоит из ядра и отдельных пакетов под каждый тип навигатора. Базовая установка:

Окно терминала
# Ядро библиотеки
npm install @react-navigation/native
# Обязательные зависимости (в Expo лучше через expo install)
npx expo install react-native-screens react-native-safe-area-context
# Навигаторы — ставим те, что нужны
npm install @react-navigation/native-stack # Stack
npm install @react-navigation/bottom-tabs # Tab
npm install @react-navigation/drawer # Drawer
# Для Drawer дополнительно нужны жесты и анимации
npx expo install react-native-gesture-handler react-native-reanimated

Минимальная рабочая настройка состоит из двух обязательных частей: корневого NavigationContainer и хотя бы одного навигатора внутри него.

App.js
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './src/screens/HomeScreen';
import DetailsScreen from './src/screens/DetailsScreen';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}

Здесь NavigationContainer оборачивает приложение и должен быть единственным на верхнем уровне. Stack.Navigator хранит экраны, а каждый Stack.Screen связывает строковое имя маршрута (name) с компонентом (component).


3. Типы навигаторов

3.1. Stack — стек экранов (push/pop)

Stack-навигатор работает по принципу стека (LIFO): новый экран кладётся поверх предыдущего, при возврате снимается верхний. Это основной паттерн для иерархических переходов: список → детали → ещё детали.

  • navigation.navigate('Details', { ... }) — перейти к экрану. Если экран уже есть в стеке, фокус вернётся к нему; если нет — он добавится.
  • navigation.push('Details', { ... }) — всегда добавить новый экземпляр экрана сверху (можно открывать «детали деталей»).
  • navigation.goBack() — вернуться на предыдущий экран (снять верхний).
  • navigation.popToTop() — вернуться к самому первому экрану стека.

Stack автоматически рисует заголовок (header) с кнопкой «Назад» и поддерживает свайп для возврата на iOS.

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';
const Stack = createNativeStackNavigator();
function HomeScreen({ navigation }) {
return (
<View>
<Text>Главный экран</Text>
<Button
title="Открыть детали"
onPress={() => navigation.navigate('Details', { id: 42 })}
/>
</View>
);
}
function DetailsScreen({ route, navigation }) {
const { id } = route.params;
return (
<View>
<Text>Детали элемента #{id}</Text>
<Button title="Назад" onPress={() => navigation.goBack()} />
</View>
);
}
export function AppStack() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Главная' }}
/>
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}

3.2. Tab — нижние вкладки

Tab-навигатор показывает панель вкладок (обычно внизу экрана) и переключает между разделами одного уровня. В отличие от стека, вкладки равноправны и не образуют истории «вперёд/назад» — пользователь просто переключается между параллельными разделами: «Лента», «Поиск», «Профиль».

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
import FeedScreen from './src/screens/FeedScreen';
import ProfileScreen from './src/screens/ProfileScreen';
const Tab = createBottomTabNavigator();
export function MainTabs() {
return (
<Tab.Navigator
screenOptions={{ tabBarActiveTintColor: '#2563eb' }}
>
<Tab.Screen
name="Feed"
component={FeedScreen}
options={{
title: 'Лента',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" color={color} size={size} />
),
}}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{ title: 'Профиль' }}
/>
</Tab.Navigator>
);
}

Важная особенность: при переключении вкладок экраны обычно не размонтируются, а сохраняют своё состояние — это удобно для лент и форм.

3.3. Drawer — боковое меню

Drawer-навигатор открывает выдвижную панель сбоку (по свайпу от края или по нажатию на «бургер»). Подходит для приложений с большим числом разделов, которые не помещаются в нижние вкладки: настройки, разделы каталога, дополнительные функции.

import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeScreen from './src/screens/HomeScreen';
import SettingsScreen from './src/screens/SettingsScreen';
const Drawer = createDrawerNavigator();
export function AppDrawer() {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Главная' }}
/>
<Drawer.Screen
name="Settings"
component={SettingsScreen}
options={{ title: 'Настройки' }}
/>
</Drawer.Navigator>
);
}

Открыть меню программно можно через navigation.openDrawer(), закрыть — navigation.closeDrawer().

3.4. Какой навигатор когда использовать

НавигаторМодель перемещенияКогда использоватьПримеры
StackСтек (push/pop), история «вперёд/назад»Иерархические переходы: от общего к частномуСписок → карточка товара → отзывы
TabПараллельные равноправные разделы2–5 основных разделов приложенияЛента / Поиск / Профиль
DrawerБоковое выдвижное менюМного разделов, второстепенная навигацияНастройки, разделы каталога, «О приложении»

На практике их комбинируют: например, Tab на верхнем уровне, а внутри каждой вкладки — свой Stack. Не нужно выбирать что-то одно.


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

4.1. Передача параметров вперёд

Параметры передаются вторым аргументом в navigate (или push) и доступны на целевом экране через route.params.

// Экран-источник
navigation.navigate('Details', { id: 42, title: 'Книга' });
// Экран-получатель (DetailsScreen)
function DetailsScreen({ route }) {
const { id, title } = route.params;
return <Text>{title} (#{id})</Text>;
}

Рекомендации по параметрам:

  • Передавайте минимум данных — обычно идентификатор (id), а сам объект подгружайте на целевом экране. Это упрощает deep links и не дублирует данные.
  • Параметры должны быть сериализуемыми (числа, строки, простые объекты). Не передавайте функции и сложные инстансы.

4.2. Значения по умолчанию

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

function DetailsScreen({ route }) {
const { id = 0 } = route.params ?? {};
// ...
}

4.3. Возврат данных на предыдущий экран

Иногда нужно вернуть результат с дочернего экрана (например, выбранный пункт из экрана-селектора). Самый простой подход — передать данные обратно через параметры предыдущего маршрута с помощью navigation.navigate, указав имя экрана-получателя:

// Экран Home открывает экран выбора города
navigation.navigate('SelectCity');
// Экран SelectCity возвращает результат на экран Home
function SelectCityScreen({ navigation }) {
function choose(city) {
navigation.navigate('Home', { selectedCity: city });
}
// ...
}
// Экран Home читает результат
function HomeScreen({ route }) {
const city = route.params?.selectedCity;
// реагируем на изменение, например через useEffect
}

Альтернатива при возврате результата — обновить параметры текущего экрана через navigation.setParams({ ... }) или использовать общий стор/контекст для обмена данными между экранами.


5. Вложенные навигаторы и организация графа экранов

Реальные приложения комбинируют навигаторы: один навигатор становится экраном другого. Классический пример — нижние вкладки, внутри каждой из которых свой стек, плюс «модальные» экраны поверх всего на уровне корневого стека.

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const RootStack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
function Tabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<RootStack.Navigator>
{/* Таб-навигатор как корневой экран — заголовок прячем */}
<RootStack.Screen
name="Root"
component={Tabs}
options={{ headerShown: false }}
/>
{/* Экран поверх вкладок (например, модальный) */}
<RootStack.Screen name="Details" component={DetailsScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}

Разделение стеков: auth-stack и app-stack (вводно)

Частая задача — показывать разные наборы экранов в зависимости от того, авторизован пользователь или нет:

  • Auth-stack — экраны входа, регистрации, восстановления пароля. Доступны неавторизованному пользователю.
  • App-stack — основной функционал приложения (вкладки, экраны), доступный после входа.

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

function RootNavigator() {
const { token } = useAuth(); // токен из контекста/стора
return (
<NavigationContainer>
{token ? <AppStack /> : <AuthStack />}
</NavigationContainer>
);
}

Когда token появляется (после входа) — дерево перестраивается на AppStack; когда исчезает (выход) — на AuthStack. Пользователь не сможет «вернуться назад» на защищённые экраны, потому что их просто нет в дереве. Подробно защиту маршрутов и флоу аутентификации разберём в лекции 11.


6. Типобезопасность маршрутов (TypeScript, кратко)

По умолчанию имена экранов и их параметры — обычные строки и объекты, и ошибку (опечатку в имени, забытый параметр) можно поймать только в рантайме. TypeScript позволяет описать карту параметров для каждого навигатора и получать проверки на этапе компиляции и автодополнение.

// Описываем экраны и их параметры
type RootStackParamList = {
Home: undefined; // без параметров
Details: { id: number }; // обязательный id
};
const Stack = createNativeStackNavigator<RootStackParamList>();
// Типизируем props экрана
import { NativeStackScreenProps } from '@react-navigation/native-stack';
type Props = NativeStackScreenProps<RootStackParamList, 'Details'>;
function DetailsScreen({ route }: Props) {
const { id } = route.params; // id выведен как number
return <Text>{id}</Text>;
}

Теперь navigation.navigate('Details', { id: 42 }) проверяется компилятором: нельзя ошибиться в имени маршрута или забыть/перепутать тип параметра. Это особенно ценно в больших проектах с десятками экранов.


Краткие итоги

  • Навигация в мобильных приложениях не предоставляется «из коробки» — её обеспечивает библиотека React Navigation.
  • Базовая модель: экраны (компоненты) живут внутри навигаторов, всё дерево оборачивается единственным NavigationContainer.
  • Три основных навигатора: Stack (иерархия, push/pop, кнопка «Назад»), Tab (равноправные разделы, нижние вкладки), Drawer (боковое меню для второстепенной навигации). На практике их комбинируют.
  • Параметры передаются через navigation.navigate('Screen', params) и читаются через route.params; передавать стоит минимум данных (обычно id). Возврат данных — через navigate обратно или общий стор.
  • Вложенные навигаторы позволяют строить сложный граф экранов; разделение на auth-stack и app-stack делают условным рендерингом по состоянию авторизации.
  • TypeScript даёт типобезопасность маршрутов: проверку имён и параметров на этапе компиляции.

Вопросы для самопроверки

  1. (Вопрос 12) Опишите основные навигационные паттерны в React Navigation: Stack, Tab, Drawer. Когда использовать каждый из них?
  2. (Вопрос 13) Как передавать параметры между экранами в React Navigation? Как вернуть данные на предыдущий экран?
  3. (Вопрос 28) Как организовать граф экранов в приложении? Опишите разделение стеков (auth-stack, app-stack).
  4. Зачем нужен NavigationContainer и почему он должен быть один?
  5. В чём разница между navigation.navigate и navigation.push?
  6. Как и зачем вкладывают навигаторы друг в друга? Приведите пример Tab + Stack.
  7. Какие преимущества даёт типизация маршрутов в TypeScript?