В React Native нет встроенного роутера. Стандарт де-факто это React Navigation, а Expo Router построен поверх него и добавляет маршрутизацию по файлам. На собесе спрашивают виды навигаторов, передачу параметров, вложенность, авторизацию и диплинки.
Навигаторы
- Native Stack (
@react-navigation/native-stack): стек экранов на нативных примитивах (UINavigationController, фрагменты). Нативные жесты и анимации, хорошая производительность. - Stack (
@react-navigation/stack): стек на JS, гибко настраиваемые анимации, но больше работы на JS-потоке. - Bottom Tabs: нижние вкладки, у каждой может быть свой стек.
- Drawer: боковое меню.
const Stack = createNativeStackNavigator<RootStackParamList>();
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Item" component={ItemScreen} />
</Stack.Navigator>
</NavigationContainer>
С седьмой версии есть и статический API (createStaticNavigation), где конфигурация описывается объектом, и типы с диплинками выводятся автоматически.
Переходы и параметры
navigation.navigate('Item', { id: '42' });
navigation.push('Item', { id: '43' }); // ещё один экран поверх, даже того же
navigation.goBack();
navigation.replace('Home');
navigation.popToTop();
const { id } = route.params;
pushвсегда добавляет новый экран.navigateможет переиспользовать уже открытый экран с тем же именем; детали этого поведения менялись между версиями, а для явного возврата к экрану из стека в седьмой версии естьpopTo.- В параметры передают сериализуемые данные: id, а не объекты и функции. Иначе ломаются диплинки, восстановление состояния и появляется предупреждение о несериализуемых значениях.
- Типизация:
RootStackParamListописывает параметры каждого экрана,NativeStackScreenProps<RootStackParamList, 'Item'>даёт типыrouteиnavigation.
Жизненный цикл экрана
Экраны стека не размонтируются, когда сверху открыли новый: предыдущий остаётся в памяти. Поэтому useEffect с пустыми зависимостями не сработает при возврате. Для этого есть useFocusEffect (выполняется при каждом фокусе экрана) и useIsFocused. Подписки, которые нужны только на видимом экране (камера, таймер), запускают в useFocusEffect и чистят в его функции очистки.
Авторизация
Рекомендуемый подход: условный рендер экранов, а не ручные переходы.
<Stack.Navigator>
{user ? (
<Stack.Screen name="Main" component={MainTabs} />
) : (
<Stack.Screen name="Login" component={LoginScreen} />
)}
</Stack.Navigator>
Вошёл пользователь, и экраны входа исчезли из дерева: кнопка «назад» на них не вернёт. В статическом API то же делается через if у групп экранов.
Deep links
linking в NavigationContainer сопоставляет адреса и экраны:
const linking = {
prefixes: ['myapp://', 'https://shop.example'],
config: { screens: { Item: 'item/:id' } },
};
Нативная часть: своя схема (myapp://) и проверенные https-ссылки (App Links на Android, Universal Links на iOS). Холодный старт по ссылке обрабатывается через начальный URL, тёплый через событие url.
Expo Router
Маршруты задаются файлами: app/index.tsx, app/item/[id].tsx, группы (tabs), общий макет в _layout.tsx. Диплинки и типизированные маршруты получаются автоматически, потому что у каждого экрана есть адрес. Под капотом всё тот же React Navigation.
Частые ошибки
- Объекты и функции в параметрах.
- Ждать размонтирования предыдущего экрана и повторного
useEffect. - Ручной
navigate('Login')при выходе вместо условного рендера. - Тяжёлые экраны в JS Stack вместо Native Stack.
Что спросят на собесе
- Native Stack против JS Stack.
navigateпротивpush, как передать и типизировать параметры.- Почему
useEffectне срабатывает при возврате на экран. - Как устроить поток авторизации.
- Как настроить диплинки, что даёт Expo Router.