Исходный код примера статьи:На GitHub.com/ есть этот гигантский нин...
Установить зависимости
$ yarn add @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
настроить
чтобы завершитьreact-native-screens
установки, добавьте следующие две строки кода вandroid/app/build.gradle
документdependencies
раздел:
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02'
чтобы завершитьreact-native-gesture-handler
, добавьте следующий код вверху файла записи, напримерindex.js
илиApp.js
:
import 'react-native-gesture-handler';
Теперь нам нужно использовать все приложениеNavigationContainer
пакет:
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
const App = () => {
return (
<NavigationContainer>
{/* Rest of your app code */}
</NavigationContainer>
);
};
export default App;
App.js
import React from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
StatusBar,
BackHandler,
} from 'react-native';
import {NavigationContainer, useFocusEffect} from '@react-navigation/native';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {createStackNavigator, HeaderBackButton} from '@react-navigation/stack';
import {IconOutline} from '@ant-design/icons-react-native';
import {Button} from '@ant-design/react-native';
import IconWithBadge from './IconWithBadge';
import HeaderButtons from './HeaderButtons';
import getActiveRouteName from './getActiveRouteName';
import getScreenOptions from './getScreenOptions';
import {navigationRef} from './NavigationService';
const HomeScreen = ({navigation, route}) => {
navigation.setOptions({
headerLeft: props => (
<HeaderBackButton
{...props}
onPress={() => {
console.log('不能再返回了!');
}}
/>
),
headerRight: () => (
<HeaderButtons>
{/* title、iconName、onPress、IconComponent、iconSize、color */}
<HeaderButtons.Item
title="添加"
iconName="plus"
onPress={() => console.log('点击了添加按钮')}
iconSize={24}
color="#ffffff"
/>
</HeaderButtons>
),
});
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, []),
);
const {author} = route.params || {};
return (
<>
<StatusBar barStyle="dark-content" />
<View style={styles.container}>
<Text>Home Screen</Text>
<Text>{author}</Text>
<Button
type="warning"
// 使用 setOptions 更新标题
onPress={() => navigation.setOptions({headerTitle: 'Updated!'})}>
Update the title
</Button>
<Button
type="primary"
onPress={() =>
// 跳转到指定页面,并传递两个参数
navigation.navigate('DetailsScreen', {
otherParam: 'anything you want here',
})
}>
Go to DetailsScreen
</Button>
<Button
type="warning"
onPress={() => navigation.navigate('SafeAreaViewScreen')}>
Go SafeAreaViewScreen
</Button>
<Button
type="primary"
onPress={() =>
navigation.navigate('CustomAndroidBackButtonBehaviorScreen')
}>
Go CustomAndroidBackButtonBehavior
</Button>
</View>
</>
);
};
const DetailsScreen = ({navigation, route}) => {
// 通过 props.route.params 接收参数
const {itemId, otherParam} = route.params;
return (
<View style={styles.container}>
<Text>Details Screen</Text>
<Text>itemId: {itemId}</Text>
<Text>otherParam: {otherParam}</Text>
<Button
type="primary"
// 返回上一页
onPress={() => navigation.goBack()}>
Go back
</Button>
<Button
type="primary"
// 如果返回上一个页面需要传递参数,请使用 navigate 方法
onPress={() => navigation.navigate('HomeScreen', {author: '杨俊宁'})}>
Go back with Params
</Button>
</View>
);
};
const SettingsScreen = ({navigation, route}) => {
return (
<SafeAreaView
style={{flex: 1, justifyContent: 'space-between', alignItems: 'center'}}>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</SafeAreaView>
);
};
const SafeAreaViewScreen = () => {
return (
<SafeAreaView
style={{flex: 1, justifyContent: 'space-between', alignItems: 'center'}}>
<Text>This is top text.</Text>
<Text>This is bottom text.</Text>
</SafeAreaView>
);
};
const CustomAndroidBackButtonBehaviorScreen = ({navigation, route}) => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
alert('物理返回键被拦截了!');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
return (
<View style={styles.container}>
<Text>AndroidBackHandlerScreen</Text>
</View>
);
};
const Stack = createStackNavigator();
const BottomTab = createBottomTabNavigator();
const BottomTabScreen = () => (
<BottomTab.Navigator
screenOptions={({route}) => ({
tabBarIcon: ({focused, color, size}) => {
let iconName;
if (route.name === 'HomeScreen') {
iconName = focused ? 'apple' : 'apple';
return (
<IconWithBadge badgeCount={90}>
<IconOutline name={iconName} size={size} color={color} />
</IconWithBadge>
);
} else if (route.name === 'SettingsScreen') {
iconName = focused ? 'twitter' : 'twitter';
}
return <IconOutline name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}>
<Stack.Screen
name="HomeScreen"
component={HomeScreen}
options={{tabBarLabel: '首页'}}
/>
<Stack.Screen
name="SettingsScreen"
component={SettingsScreen}
options={{tabBarLabel: '设置'}}
/>
</BottomTab.Navigator>
);
const App = () => {
const routeNameRef = React.useRef();
return (
<>
<NavigationContainer
ref={navigationRef}
onStateChange={state => {
const previousRouteName = routeNameRef.current;
const currentRouteName = getActiveRouteName(state);
if (previousRouteName !== currentRouteName) {
console.log('[onStateChange]', currentRouteName);
if (currentRouteName === 'HomeScreen') {
StatusBar.setBarStyle('dark-content'); // 修改 StatusBar
} else {
StatusBar.setBarStyle('dark-content'); // 修改 StatusBar
}
}
// Save the current route name for later comparision
routeNameRef.current = currentRouteName;
}}>
<Stack.Navigator
initialRouteName="HomeScreen"
// 页面共享的配置
screenOptions={getScreenOptions()}>
<Stack.Screen
name="BottomTabScreen"
component={BottomTabScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="DetailsScreen"
component={DetailsScreen}
options={{headerTitle: '详情'}} // headerTitle 用来设置标题栏
initialParams={{itemId: 42}} // 默认参数
/>
<Stack.Screen
name="SafeAreaViewScreen"
component={SafeAreaViewScreen}
options={{headerTitle: 'SafeAreaView'}}
/>
<Stack.Screen
name="CustomAndroidBackButtonBehaviorScreen"
component={CustomAndroidBackButtonBehaviorScreen}
options={{headerTitle: '拦截安卓物理返回键'}}
/>
</Stack.Navigator>
</NavigationContainer>
</>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
Регистр имени маршрута не имеет значения — вы можете использовать строчные буквы
home
или заглавные буквыHome
, это зависит от ваших предпочтений. Мы предпочитаем писать имена маршрутов с большой буквы. Мы предпочитаем использовать наши названия маршрутов.
Методы прыжка
navigate
,push
,goBack
,popToTop
Можно использовать
navigation.setParams
способ обновления параметров страницы
мы можем пройти
options={({ route, navigation }) => ({ headerTitle: route.params.name })}
способ использования параметров в заголовках
мы можем использовать
navigation.setOptions
Обновить конфигурацию страницы
-
Stack.Navigator
-
initialRouteName
: используется для настройкиStack.Navigator
первоначальный маршрут -
screenOptions
: объект конфигурации общего доступа к странице
-
-
Stack.Screen
-
name
: название страницы -
component
: соответствующий компонент страницы -
options
: объект конфигурации страницы -
initialParams
: параметр по умолчанию
-
HeaderButtons.js
использоватьreact-navigation-header-buttons
Вы можете настроить свой собственный компонент Header Button с любым компонентом Icon.Для удобства демонстрации я использую@ant-design/icons-react-native
:
import React from 'react';
import {
HeaderButtons as RNHeaderButtons,
HeaderButton as RNHeaderButton,
Item,
} from 'react-navigation-header-buttons';
import {IconOutline} from '@ant-design/icons-react-native';
const HeaderButton = props => {
return (
<RNHeaderButton
{...props}
IconComponent={IconOutline}
iconSize={props.iconSize || 23}
color={props.color || '#000000'}
/>
);
};
const HeaderButtons = props => {
return <RNHeaderButtons HeaderButtonComponent={HeaderButton} {...props} />;
};
HeaderButtons.Item = Item;
export default HeaderButtons;
IconWithBadge.js
import React from 'react';
import {View} from 'react-native';
import {Badge} from '@ant-design/react-native';
const IconWithBadge = ({children, badgeCount, ...props}) => {
return (
<View style={{width: 24, height: 24, margin: 5}}>
{children}
<Badge
{...props}
style={{position: 'absolute', right: -6, top: -3}}
text={badgeCount}
/>
</View>
);
};
export default IconWithBadge;
getActiveRouteName.js
/**
* Gets the current screen from navigation state
* @param state
*/
const getActiveRouteName = state => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return getActiveRouteName(route.state);
}
return route.name;
};
export default getActiveRouteName;
getScreenOptions.js
import {TransitionPresets} from '@react-navigation/stack';
const getScreenOptions = () => {
return {
headerStyle: {
backgroundColor: '#ffffff',
}, // 一个应用于 header 的最外层 View 的 样式对象
headerTintColor: '#000000', // 返回按钮和标题都使用这个属性作为它们的颜色
headerTitleStyle: {
fontWeight: 'bold',
},
headerBackTitleVisible: false,
headerTitleAlign: 'center',
cardStyle: {
flex: 1,
backgroundColor: '#f5f5f9',
},
...TransitionPresets.SlideFromRightIOS,
};
};
export default getScreenOptions;
NavigationService.js
import React from 'react';
export const navigationRef = React.createRef();
const navigate = (name, params) => {
navigationRef.current && navigationRef.current.navigate(name, params);
};
const getNavigation = () => {
return navigationRef.current && navigationRef.current;
};
export default {
navigate,
getNavigation,
};
Жизненный цикл страницы и навигация React
StackNavigator, содержащий страницы A и B, при переходе к A,componentDidMount
будет вызван метод; при переходе к B,componentDidMount
Метод также будет вызван, но A все еще остается загруженным в стеке, егоcomponentWillUnMount
тоже не позовут.
При прыжке из B в A, BcomponentWillUnmount
метод будет вызван, но метод AcomponentDidMount
Метод не будет вызываться, так как A в это время все еще загружен.
Реагировать на события жизненного цикла навигации
addListener
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
useFocusEffect
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, []),
);
Скрыть заголовок/вкладку
-
headerMode:"none"
: hide Header forStack.Navigator
-
headerShown:false
: hide Header forStack.Screen
-
tabBar={() => null}
: hide TabBar forBottomTab.Navigator
import {NavigationContainer, useFocusEffect} from '@react-navigation/native';
import {createStackNavigator, TransitionPresets, HeaderBackButton} from '@react-navigation/stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
const Stack = createStackNavigator();
const BottomTab = createBottomTabNavigator();
export default App = () => {
<NavigationContainer>
<Stack.Navigator headerMode="none">
<Stack.Screen
...
options={{ headerShown: false }}
/>
<Stack.Screen ...>
{() => (
<BottomTab.Navigator
...
tabBar={() => null}
>
...
</BottomTab.Navigator>
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
}
Панель состояния TabBar отличается
Как правило, мы рассмотрим специальный Tabbar.
const getActiveRouteName = state => {
const route = state.routes[state.index];
if (route.state) {
// Dive into nested navigators
return getActiveRouteName(route.state);
}
return route.name;
};
const App = () => {
const ref = React.useRef(null);
return (
<>
{/* 访问 ref.current?.navigate */}
<NavigationContainer
ref={ref}
onStateChange={state => {
const previousRouteName = ref.current;
const currentRouteName = getActiveRouteName(state);
if (previousRouteName !== currentRouteName) {
console.log('[onStateChange]', currentRouteName);
if (currentRouteName === 'HomeScreen') {
StatusBar.setBarStyle('dark-content'); // 修改 StatusBar
} else {
StatusBar.setBarStyle('dark-content'); // 修改 StatusBar
}
}
}}
>
</NavigationContainer>
</>
)
}
Прослушайте физический ключ возврата Android
import {View, Text, BackHandler} from 'react-native';
const CustomAndroidBackButtonBehaviorScreen = ({navigation, route}) => {
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
alert('物理返回键被拦截了!');
return true;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, []),
);
return (
<View style={styles.container}>
<Text>AndroidBackHandlerScreen</Text>
</View>
);
};
Доступ в дочерних компонентахnavigation
мы можем пройтиuseNavigation()
крюк для доступа к навигации, больше не нужно проходить несколько слоевnavigation
import React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';
function GoToButton({ screenName }) {
const navigation = useNavigation();
return (
<Button
title={`Go to ${screenName}`}
onPress={() => navigation.navigate(screenName)}
/>
);
}
Передать дополнительные свойства на страницу
<Stack.Screen
name="HomeScreen"
options={{headerTitle: '首页'}}>
{props => <HomeScreen {...props} extraData={{author: '杨俊宁'}} />}
</Stack.Screen>
Получить высоту заголовка
import { useHeaderHeight } from '@react-navigation/stack'
const App = () => {
const HeaderHeight = useHeaderHeight() // 获取Header Height
return(...)
}
export default App
Продолжайте использовать компоненты класса
Учитывая, что сцена не подходит для хуков, но дело очень срочное, мы можем инкапсулировать слой поверх компонента класса для поддержки компонента хуков в React Navigation, Причина этого в том, что в React Navigation 5 мы можем передать толькоuseHeaderHeight()
способ получить высоту строки заголовка.
class Albums extends React.Component {
render() {
return <ScrollView ref={this.props.scrollRef}>{/* content */}</ScrollView>;
}
}
// 封装并导出
export default function(props) {
const ref = React.useRef(null);
useScrollToTop(ref);
return <Albums {...props} scrollRef={ref} />;
}