在App中,底部 TabBar 導航和頂部的導航欄導航是最常見的頁面導航方式,而 React Native 官方推薦的第三方庫是 @react-navigation ,正好今年 React Navigation 發佈了5.0版本,與前面的版本差別還挺大,不過使用上更方便了。本文主要講解 React Navigation 5.0及以上版本的使用。

安裝

安裝核心包

# NPM
npm install @react-navigation/native
# Yarn
yarn add @react-navigation/native

安裝依賴

# NPM
npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
#Yarn
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

React Native0.60及以上版本是自動鏈接庫的,不需要手動運行 react-native link ,但如果你用 React Native 開發的是iOS,還需要手動安裝 pods 來完成庫的鏈接:

npx pod-install ios

把下面這行代碼放在入口文件的頂部,比如 index.js 或者 App.js

import 'react-native-gesture-handler';

最後需要用 NavigationContainer 將整個App包裹起來,這個代碼一般也是放在入口文件,類似下面這樣:

import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
  );
}

注意:如果你同時也使用了 Redux 框架,需要把 Provider 放在最外層,將 NavigationContainer 包裹在次外層,類似下面這樣:

export default class App() {
  return (
    <Provider store={store}>
      <NavigationContainer>
        {/* Screen configuration */}
      </NavigationContainer>
    </Provider>
  );
}

使用

React Navigation有多種導航方式: Stack NavigationTab NavigationDrawer Navigation 。這裏主要講 Stack NavigationTab Navigation

頂部導航欄導航

首先需要安裝相應的庫:

#NPM
npm install @react-navigation/stack
#Yarn
yarn add @react-navigation/stack

Stack Navigation 使用上相對來說比較簡單,只需要把需要導航的頁面組件封裝成 Stack.Screen ,然後包裹在 Stack.Navigator 就可以了,最上面的 Stack.Screen 就是默認頁面。

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen key="Home" name="Home" component={HomeScreen} />
      </Stack.Navigator>
      <Stack.Screen key="detail" name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

也可以在 Stack.Screen 中設置導航欄的屬性,設置導航欄屬性 options 有兩種方式,一種是帶 route 參數的,一種是直接設置

/* 帶route參數的options */
<Stack.Screen
    key={name}
    name={name}
    component={component}
    options={({route}) => ({
        headerTitle: route.name,
        headerStyle: {
            backgroundColor: '#01aaff',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
            fontWeight: 'bold',
        },
        headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        headerShown: true
    })}
/>
/* 直接設置options */
<Stack.Screen
    key={name}
    name={name}
    component={component}
    options={{
        headerTitle: 'name',
        headerStyle: {
            backgroundColor: '#01aaff',
        },
        headerTintColor: '#fff',
        headerTitleStyle: {
            fontWeight: 'bold',
        },
        headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        headerShown: true
    }}
/>

注意: React 框架使用的是虛擬 DOM ,使用 diff 算法刷新頁面顯示,在 Stack.Screen 中,需要添加 key 屬性,否則會報警告。

在需要跳轉到其他頁面時只需要以下代碼就可以了,後面跟的是參數。

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => {
          /* 1. Navigate to the Details route with params */
          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });
        }}
      />
    </View>
  );
}

如果是用 class 方式創建的頁面,代碼會像下面這樣:

export default class HomeScreen extends Component {
    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>Home Screen</Text>
                <Button
                    title="Go to Details"
                    onPress={() => {
                        this.props.navigation.navigate('Details', {
                            itemId: 86,
                            otherParam: 'anything you want here',
                        });
                    }}
                />
            </View>
        );
    }
}

注意:導航跳轉可以使用 navigation.navigate 或者 navigation.push 。如果使用 navigate ,會查找當前堆棧中是否有名字一樣的路由,如果沒有才創建新的路由並跳轉,而 push 則會直接創建一個新的路由並跳轉,也就是說可以重複多次跳轉同一個頁面。

堆棧導航的返回方式如下:

// 返回到上一頁面
navigation.goBack()
// 返回到堆棧裏第一個頁面
navigation.popToPop()

底部導航欄導航

除了頂部導航欄,最常用的就是底部導航欄了,首先也需要安裝相應的庫:

# NPM
npm install @react-navigation/bottom-tabs
# Yarn
yarn add @react-navigation/bottom-tabs

底部導航欄的使用方法類似,需要將這些頁面組件包裝成 Tab.Screen ,導航則由框架內部完成,不需要手動控制:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

底部導航欄屬性可以按如下方式設置:

<Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;

            if (route.name === 'Home') {
              iconName = focused
                ? 'ios-information-circle'
                : 'ios-information-circle-outline';
            } else if (route.name === 'Settings') {
              iconName = focused ? 'ios-list-box' : 'ios-list';
            }
            return <Ionicons name={iconName} size={size} color={color} />;
          },
        })}
        tabBarOptions={{
          activeTintColor: 'tomato',
          inactiveTintColor: 'gray',
        }}
      >
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>

頂部堆棧導航欄與底部導航欄嵌套

Stack NavigationTab Navigation 可以相互多層嵌套,比如登錄頁跳轉到帶底部導航欄的主頁面,需要把登錄相關面和 Tab.navigator 一同放到同一個堆棧中,但官方不推薦這種方式,嵌套層次太多會導致維護起來特別麻煩。官方推薦使用一個變量控制當前顯示的頁面,把不相關的模塊隔離在不同的堆棧中,類似下面這樣:

const commonScreens = {
  Help: HelpScreen,
};

const authScreens = {
  SignIn: SignInScreen,
  SignUp: SignUpScreen,
};

const userScreens = {
  Home: HomeScreen,
  Profile: ProfileScreen,
};

<Stack.Navigator>
  {Object.entries({
    ...commonScreens,
    ...(isLoggedIn ? userScreens : authScreens),
  }).map(([name, component]) => (
    <Stack.Screen name={name} component={component} />
  ))}
</Stack.Navigator>;

isLoggedIn 這樣的全局變量動態刷新頁面使用 Redux 架構實現起來會簡單一些,具體實現可以參考 這篇教程

相關文章