React Navigation: стек, табы, параметры и deep links
Навигация — обязательный блок вопросов: в React Native она не встроена и живёт отдельной библиотекой.
React Navigation — стандарт де-факто для навигации в RN. Экраны организованы в навигаторы (стек, табы, drawer), а их текущее состояние — обычное сериализуемое дерево.
Вопрос
«Как устроена навигация в React Native? Чем стековый навигатор отличается от табового, как передавать параметры и как обновить данные при возврате на экран?»
Что на самом деле проверяет интервьюер
Понимаете ли вы, что предыдущие экраны остаются живыми, и умеете ли работать с фокусом. Это источник двух вечных багов: «данные не обновились после редактирования» и «на экране в фоне продолжает крутиться таймер».
Развёрнутый ответ
Минимальная схема
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
function FeedStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Feed" component={FeedScreen} options={{ title: 'Лента' }} />
<Stack.Screen name="Post" component={PostScreen} />
</Stack.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="FeedTab" component={FeedStack} options={{ title: 'Лента' }} />
<Tab.Screen name="ProfileTab" component={ProfileScreen} options={{ title: 'Профиль' }} />
</Tab.Navigator>
</NavigationContainer>
);
}
Полезно отметить: native-stack использует нативные контроллеры навигации платформы, поэтому переходы и жесты ощущаются «родными», в отличие от старого JS-стека.
Параметры маршрута
// откуда переходим
navigation.navigate('Post', { postId: 42 });
// куда переходим
function PostScreen({ route, navigation }) {
const { postId } = route.params;
// ...
}
// вернуть результат назад — не колбэком, а через параметры предыдущего экрана
navigation.navigate('Feed', { createdPostId: newId });
Правило: в параметрах — только сериализуемые примитивы, прежде всего идентификаторы. Состояние навигации может сохраняться и восстанавливаться, поэтому функции и большие объекты туда класть нельзя.
navigate против push и как вернуться
navigate('Post')— перейти к экрану; если такой уже есть в стеке, вернуться к нему.push('Post')— всегда положить сверху новый экземпляр (цепочка «товар — похожий товар»).goBack()— на шаг назад,popToTop()— в корень стека.replace('Home')— заменить текущий экран (типично после логина, чтобы нельзя было вернуться назад).reset(...)— полностью перестроить стек, например при выходе из аккаунта.
Экраны живут в фоне
import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';
useFocusEffect(
useCallback(() => {
const timer = setInterval(refresh, 30000);
return () => clearInterval(timer); // при уходе с экрана таймер гасим
}, [refresh])
);
Есть и хук useIsFocused(), если нужно просто не рендерить тяжёлое содержимое на неактивном экране. Отдельно стоит упомянуть unmountOnBlur у табов — им лечат «висящие» экраны.
Deep links
Глубокие ссылки нужны для писем, push-уведомлений и рекламных кампаний. Конфигурация связывает URL с маршрутами:
const linking = {
prefixes: ['myapp://', 'https://example.com'],
config: {
screens: {
FeedTab: {
screens: {
Feed: 'feed',
Post: 'post/:postId',
},
},
ProfileTab: 'profile',
},
},
};
<NavigationContainer linking={linking} fallback={<Splash />}>...</NavigationContainer>
На стороне платформ нужно ещё зарегистрировать схему и домены: на iOS — URL types и Associated Domains для universal links, на Android — intent-filter с autoVerify для App Links. Проверяют так:
npx uri-scheme open "myapp://post/42" --ios
adb shell am start -W -a android.intent.action.VIEW -d "myapp://post/42" com.example.myapp
Типизация в TypeScript
export type RootStackParamList = {
Feed: undefined;
Post: { postId: number };
};
// теперь навигация и route.params проверяются компилятором
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
Типичные ошибки кандидатов
- Передают колбэк через параметры маршрута — это ломает восстановление состояния и вызывает предупреждение.
- Используют
useEffectс пустым массивом там, где нуженuseFocusEffect: экран не размонтировался, эффект не повторится. - Не различают
navigateиpush, из-за чего цепочка похожих экранов «схлопывается». - Думают, что deep link настраивается только в JS, забывая про схемы в Info.plist и intent-filter в манифесте.
Как ответить кратко
«Навигация — это React Navigation: NavigationContainer сверху, внутри навигаторы — native-stack, табы, drawer. Параметры передаю через navigate с сериализуемыми данными, только id, никаких функций и больших объектов; результат возвращаю параметрами предыдущего экрана. navigate переходит к существующему экрану, push всегда добавляет новый; replace и reset — для логина и выхода. Важно, что экраны остаются смонтированными, поэтому обновление и таймеры вешаю на useFocusEffect. Deep links описываю конфигом linking, плюс регистрирую схему в Info.plist и intent-filter в манифесте.»