react-navigation (v2) 初級

本文是在react-navigation v2版本基礎上編輯的。中文文檔傳送口

為了便于牢記react-navigation的功能,我將其分成了幾個小塊:

  • StackNavigator
    堆棧導航
  • Tab Navigator
    Tab菜單
  • Drawer Navigator
    側邊欄菜單
  • SwitchNavigator
  • navigation
    navigation屬性
  • withNavigation
    傳遞navigation
  • android物理返回鍵重寫
  • Stack navigator、Tab navigator等相互嵌套
  • SafeAreaView

1.StackNavigator

stack navigator 為你的應用提供了一種在屏幕之間切換并管理導航歷史記錄的方式。 如果您的應用程序只使用一個 stack navigator ,則它在概念上類似于Web瀏覽器處理導航狀態的方式 - 當用戶與它進行交互時,應用程序會從導航堆棧中新增和刪除頁面,這會導致用戶看到不同的頁面。 Web瀏覽器和 React Navigation 工作原理的一個主要區別是:React Navigation 的 stack navigator 提供了在 Android 和 iOS 設備上,在堆棧中的路由之間導航時你期望的手勢和動畫。

創建一個stack navigator
通過createStackNavigator(RouteConfigs, StackNavigatorConfig)函數,返回一個React組件。

RouteConfigs:

createStackNavigator({
  // For each screen that you can navigate to, create a new entry like this:
  Profile: {
    // `ProfileScreen` is a React component that will be the main content of the screen.
    screen: ProfileScreen,
    // When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.

    // Optional: When deep linking or using react-navigation in a web app, this path is used:
    path: 'people/:name',
    // The action and route params are extracted from the path.

    // Optional: Override the `navigationOptions` for the screen
    navigationOptions: ({ navigation }) => ({
      title: `${navigation.state.params.name}'s Profile'`,
    }),
  },

  ...MyOtherRoutes,
});

screen添加到stack navigation中時,以key:object的形式添加,key必須唯一,object必須屬性:screen,選配屬性path、navigationOptions。此處的navigationOptions是針對本screen的配置。

StackNavigatorConfig:


StackNavigatorConfig
  • initialRouteName:設置堆棧的默認屏幕。 必須匹配路由配置中的某個 Key。
  • initialRouteParams: 初始路由的參數
  • navigationOptions: 用于屏幕的默認導航選項。是對stack navigator內所有screen的配置,可重寫。
  • mode: 定義渲染和轉換的樣式;
    card - 使用標準的 iOS 和 Android 屏幕轉換。 這是默認設置。
    modal -使屏幕從底部滑動, 這是一個常見的 iOS 模式。僅在 iOS 上工作, 對 Android 沒有影響。
  • headerMode: 指定標題欄的呈現方式:
    float -呈現一個位于頂部的單個標題欄, 并在屏幕被更改時進行動畫顯示。這是 iOS 上常見的模式。
    screen -每個屏幕都有一個標頭, 并且標題欄隨屏幕一起淡入和淡出。這是 Android 的常見模式。
    none -不會呈現標題欄。

2. Tab Navigator

屏幕底部的一個簡單的選項卡欄, 可以在不同的路由之間切換。路由被懶加載--他們的屏幕組件在第一次聚焦之前不會載入。(以底部選項卡進行說明)

tab navigator
  • 創建方式:有三種,分別對應了底部選項卡欄、屏幕底部的材料設計主題標簽欄、屏幕頂部的材料設計主題標簽欄。
  • initialRouteName:第一次加載時初始選項卡路由的 routeName。
  • order:定義選項卡順序的 routeNames 數組。
  • backBehavior:控制 "返回" 按鈕是否會導致 Tab 頁切換到初始 Tab 頁? 如果是, 設置為 initialRoute, 否則 none。 默認為 initialRoute的行為。
  • tabBarComponent: -可選,覆蓋用作標簽欄的組件。
  • BottomTabNavigatorConfig:tabbar配置選項。
  • navigationOptions:導航器內部的navigationOptions。

示例代碼:

const MineStack = createStackNavigator(
    {
        Mine:MineScreen,
        Setting:SettingScreen
    }
)

const HomeStack = createStackNavigator(
    {
        Home:HomeScreen
    }
)

const TabNavigator = createBottomTabNavigator(
    {
        Home2:HomeStack,
        Detail: DetailScreen,
        Mine: MineStack,
    }
)

需要注意的是,TabNavigator中的tab頁面是沒有標題欄的。

3. Drawer Navigator

側邊抽屜式菜單導航。

drawer navigator
  • drawerWidth:抽屜的寬度或返回它的函數。

  • drawerPosition:選項為 左 或 右. 默認值為 左 位置。

  • contentComponent:用于呈現抽屜內容 (例如, 導航項) 的組件。 接收用于抽屜的 navigation 支柱。 默認為 DrawerItems。

  • contentOptions:配置抽屜內容

  • useNativeAnimations:啟用本機動畫。默認值為 true。

  • drawerBackgroundColor:使用某種顏色的抽屜背景。默認值為 白色。

  • initialRouteName:第一次加載時初始選項卡路由的 routeName。

  • order:定義選項卡順序的 routeNames 數組。

  • backBehavior:控制 "返回" 按鈕是否會導致 Tab 頁切換到初始 Tab 頁? 如果是, 設置為 initialRoute, 否則 none。 默認為 initialRoute的行為。

  • navigationOptions:頁面導航選項.

  • title:可用作headerTitle和tabBarLabel的后備的通用標題。

  • drawerLabel:字符串,React 元素或給定{focused:boolean,tintColor:string}的函數返回一個React.Node,以顯示在抽屜邊欄中。 未定義時,使用場景title

  • drawerIcon:React 元素或給定{ focused: boolean, tintColor: string }的函數返回一個 React.Node,用于在標簽欄中顯示。

  • drawerLockMode:指定抽屜的鎖定模式。 這也可以通過在頂級路由器上使用 screenProps.drawerLockMode 動態更新。

  • open/close:通過navigation的api開啟/關閉drawer navigator。

4. SwitchNavigator

SwitchNavigator的目的是一次只顯示一個屏幕。默認情況下,它不處理返回操作,并在你切換時將路由重置為默認狀態。

SwitchNavigator
  • initialRouteName:第一次加載時初始選項卡路由的 routeName。
  • resetOnBlur:切換離開屏幕時,重置所有嵌套導航器的狀態。 默認為true。
  • backBehavior:控制 "返回" 按鈕是否會導致 Tab 頁切換到初始 Tab 頁? 如果是, 設置為 initialRoute, 否則 none。 默認為none行為。

5. Navigation Prop

重要的是要強調導航道具沒有傳遞到所有組件;只有屏幕組件自動接收navigation!

Navigation Prop
  • navigate:轉到另一個屏幕,如果已存在,轉到已存在的屏幕,如果不存在,則增加。
navigation.navigate({routeName, params, action, key})
navigation.navigate(routeName, params, action)
  • addListener:訂閱導航生命周期的更新。
    React Navigation發射事件以屏幕訂閱它們的組件:willBlur、willFocus、didFocus、didBlur
const didBlurSubscription = this.props.navigation.addListener(
  'didBlur',
  payload => {
    console.debug('didBlur', payload);
  }
);

// Remove the listener when you are done
didBlurSubscription.remove();

The JSON payload:

{
  action: { type: 'Navigation/COMPLETE_TRANSITION', key: 'StackRouterRoot' },
  context: 'id-1518521010538-2:Navigation/COMPLETE_TRANSITION_Root',
  lastState: undefined,
  state: undefined,
  type: 'didBlur',
};
  • isFocused:如果屏幕聚焦則返回true,否則返回false。
  • state:屏幕的當前狀態/路由
{
  // the name of the route config in the router
  routeName: 'profile',
  //a unique identifier used to sort routes
  key: 'main0',
  //an optional object of string options for this screen
  params: { hello: 'world' }
}
  • dispatch:向路由器發送一個動作
import { NavigationActions } from 'react-navigation';

const navigateAction = NavigationActions.navigate({
  routeName: 'Profile',
  params: {},

  // navigate can have a nested navigate action that will be run inside the child router
  action: NavigationActions.navigate({ routeName: 'SubProfileRoute' }),
});
this.props.navigation.dispatch(navigateAction);
  • push:添加新路由到堆棧,不管堆棧中是否已有相同key的路由。
navigation.push(routeName, params, action)
  • pop:帶你到堆棧中的前一個屏幕。如果您提供一個數字“n”,它將指定多少屏幕將您帶回堆棧。
navigation.pop(n)
  • popToTop:將其調用以跳回堆棧中的頂部路徑,關閉所有其他屏幕。。
  • replace:用新的路由替換當前的路由。
navigation.replace(routeName, params, action)

如果當前導航器是堆棧導航器,navigation prop才具有額外的 push、pop、popToTop、replace。

6. withNavigation

withNavigation是一個高階組件,它可以將 navigation 這個 prop 傳遞到一個包裝的組件。 當你無法直接將navigation 這個 prop 傳遞給組件,或者不想在深度嵌套的子組件中傳遞它時,它將非常有用。
withNavigation (Component)返回一個Component

import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';

class MyBackButton extends React.Component {
  render() {
    return <Button title="Back" onPress={() => { this.props.navigation.goBack() }} />;
  }
}

// withNavigation returns a component that wraps MyBackButton and passes in the
// navigation prop
export default withNavigation(MyBackButton);

7. android物理返回鍵重寫

默認情況下,當用戶按下 Android 物理返回鍵時,react-navigation 會返回到新頁面,如果沒有可返回的頁面,則退出應用。 這是一個合理的默認行為,但有些情況下你可能想要實現自定義處理。

推薦的第三方依賴:react-navigation-backhandler

我的方式是:定義BaseComponent,需要重寫android返回鍵的,集成該組件。

let lastBackPressed = 0;

export default class BaseComponent extends Component {

    _didFocusSubscription;
    _willBlurSubscription;

    constructor(props) {
        super(props);
        this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
            BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
        );
    }

    componentDidMount() {
        this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
            BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
        );
    }

    componentWillUnmount() {
        this._didFocusSubscription && this._didFocusSubscription.remove();
        this._willBlurSubscription && this._willBlurSubscription.remove();
    }

    onBackButtonPressAndroid = () => {
        let now = new Date().getTime();
        if(now - lastBackPressed < 2500) {
            return false;
        }
        lastBackPressed = now;
        ToastAndroid.show('再點擊一次退出應用',ToastAndroid.SHORT);
        return true;
    };

}

其中使用到了navigation的addListener屬性。
onBackButtonPressAndroid返回true,則react-navigation將不會接收到返回事件,當返回false時,react-navigation將處理返回事件,返回上個路由或者退出應用。

8. Stack navigator、Tab navigator等相互嵌套

navigator之間的相互嵌套示例:

const DrawNavigator = createDrawerNavigator(
    {
        Draw1:DrawScreen1,
        Draw2: DrawScreen2,
    }
)

const MineStack = createStackNavigator(
    {
        Mine:MineScreen,
        Setting:SettingScreen
    }
)

const HomeStack = createStackNavigator(
    {
        Home:HomeScreen
    }
)

HomeStack.navigationOptions={tabBarLabel: 'Home!',}

//****stack navigator嵌套在Tab navigator中
const TabNavigator = createBottomTabNavigator(
    {
        Home2:HomeStack,
        Detail: DetailScreen,
        Mine: MineStack,
    },

)

TabNavigator.navigationOptions = {header:null}

//****Draw navigator 和Tab navigator 嵌套在stack navigator中
const RootStack = createStackNavigator(
    {
        Draw:DrawNavigator,
        Tab: TabNavigator
    },{
        initialRouteName: 'Tab',
    }
);


export default class App extends React.Component {
    
    render() {
        return <RootStack />;
    }

}

關注下TabNavigator.navigationOptions = {header:null}代碼設置,其效果是將父navigator(stack navigator)的標題欄隱藏。
Why?
TabNavigator是一個React組件,TabNavigator.navigationOptions配置的是TabNavigator組件的的導航配置,是相對于父navigator(組件)的導航配置。我們其稱之為TabNavigator外部navigationOptions
那么內部的navigationOptions如何理解呢?

TabNavigator的內部navigationOptions指的是tab navigator包含的組件(screen)中的tabBar相關的configs(tab navigator包含的screen是沒有標題欄的)。那么tabNavigator的內部navigationOptions該如何設置呢?示例代碼如下:

const TabNavigator = createBottomTabNavigator(
    {
        Home2:HomeStack,
        Detail: DetailScreen,
        Mine: MineStack,
    },{
        navigationOptions:{
            title:'Test'
        }
    }

)

上述代碼中navigationOptions是針對TabNavigator內部所有的screen而言,可在screen中重寫效果。

關于外部navigationOptions內部navigationOptions的概念,同樣適用于其他navigator,如HomeStack.navigationOptions={tabBarLabel: 'Home!',},HomeStack是Tab navigator的包含的一個組件,外部的navigationOptions就是設置這個組件的tabbar配置。

9. SafeAreaView

默認情況下,React Navigation 會幫助確保您的應用程序在iPhoneX上正確顯示。 它通過在可能與傳感器集群(“齊劉海”)或 Home 主頁指示器交互的UI元素內部使用SafeAreaView來實現。

當我們隱藏/自定義導航欄或選項卡欄時,可能存在以下問題:


錯誤示例

Landscape Mode(橫屏模式)時,可能的錯誤:


錯誤示例

此時,我們可以 通過使用SafeAreaView包裝內容,避免這些問題。示例代碼如下:

import { SafeAreaView } from 'react-navigation';

class App extends Component {
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <Text style={styles.paragraph}>
          This is top text.
        </Text>
        <Text style={styles.paragraph}>
          This is bottom text.
        </Text>
      </SafeAreaView>
    );
  }
}

使用 forceInset 獲得更多控制
在某些情況下,你可能需要更多控制應用哪些填充。 例如,你可以通過將forceInset 這個 prop 傳遞給SafeAreaView來移除底部填充。

<SafeAreaView style={styles.container} forceInset={{ bottom: 'never' }}>
  <Text style={styles.paragraph}>
    This is top text.
  </Text>
  <Text style={styles.paragraph}>
    This is bottom text.
  </Text>
</SafeAreaView>

forceInset使用 key(取值:top | bottom | left | right | vertical | horizontal)和value(取值:'always' | 'never')組成一個對象 或者,您可以通過傳遞一個整數來完全重寫填充。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容