ReactNative ? react-navigation 3.x

React Native 中文網
React Navigation 官網

簡介

一、安裝步驟
1、打開終端,cd到工程所在文件夾

2、yarn add react-navigation

3、yarn add react-native-gesture-handler

4、react-native link react-native-gesture-handler
二、關于React Native工程的一些知識點

1、在ReactNative中,所有的類都視為組件。

2、我們在初始化React Native新項目的時候,會有一個App.js文件。我們運行Xcode顯示在屏幕上的根視圖就是在這個文件中通過export default導出的那個組件。

3、在3.x的版本中,需要通過createAppContainer方法將設置好路由的導航器包裝為組件。
否則報錯如下:

不使用createAppContainer方法報錯

4、組件的導入,在es6+中,通過import導入其他自定義組件

三、官網Demo分析

// In App.js in a new project

import React from "react";
import { View, Text } from "react-native";
import { createStackNavigator, createAppContainer } from "react-navigation";

class HomeScreen extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: "center", justifyContent: "center"   }}>
        <Text>Home Screen</Text>
      </View>
    );
  }
}

const AppNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen
  }
});

export default createAppContainer(AppNavigator);

結合上面的知識點,分析如下

  • 在App.js文件中
  • 導入react-navigation
  • 定義HomeScreen組件
  • 定義導航器組件,設置路由
  • 將導航器組件包裝后導出,此時導航控制器就是應用的根視圖控制器

運行結果:


導航控制器

四、注意點

1、在導入navigation組件后通常需要重啟終端,否則報錯。

2、我們在使用WebStorm打開初始化工程項目時,都會默認導出名為App的組件

export default class App extends Component<Props> {
  render() {
    return (
      <View></View>
    );
  }
}

與直接導出 stack navigator 相比,對應用程序根部的組件進行更多的控制通常更有用,所以我們導出一個只渲染了 stack navigator 的組件。

const AppNavigator = createStackNavigator({
      Home: {
        screen: HomeScreen
      }
    });

const AppContainer = createAppContainer(AppNavigator);

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

這里需要注意,我們不可以像書寫StyleSheet那樣把

const AppNavigator = createStackNavigator({
      Home: {
        screen: HomeScreen
      }
    });

寫到App組件的下方,這樣做報錯如下:


將導航器寫在下方報錯

3、頁面組件就是創建StackNavigator配置路由的組件。只有頁面組件才會擁有導航的一些屬性。

頁面切換

一、跳轉新的頁面
<Button
     title="Go to Details"
     onPress={() => this.props.navigation.navigate('Details')}
 />

如果我們使用未在 stack navigator 中定義的路由名稱調用this.props.navigation.navigate 方法,則不會發生任何事情。 換句話說,我們只能導航到已經在我們的 stack navigator 上定義的路由; 不能隨便導航到任意組件。

二、多次導航到同一路由
class DetailsScreen extends React.Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Details Screen</Text>
        <Button
          title="Go to Details... again"
          onPress={() => this.props.navigation.navigate('Details')}
        />
      </View>
    );
  }
}

如果我們使用this.props.navigation.navigate導航到同一路由是不起作用的。
我們需要使用this.props.navigation.push來實現

每次調用 push 時, 我們會向導航堆棧中添加新路由。

三、總結

1、我們可以使用this.props.navigation.navigate('RouteName')跳轉到在stack navigator中已經定義的路由名稱對應的新頁面,但要注意新路由和當前路由不相等。
2、我們可以使用this.props.navigation.navigate('RouteName')返回堆棧中的現有頁面,如果有多個相同頁面,則返回到最底層的頁面

生命周期

React Navigation中,react的生命周期仍然適用。

只要頁面處于加載的狀態,react生命周期中的componentDidMountcomponentWillUnmount方法就不會被調用,這類似于iOS開發中的ViewDidLoad方法和dealloc方法,在生命周期中,只會調用一次。那么類比OC,我們如何監聽ViewWillAppearViewDidAppearViewWillDisappearViewDidDisappear的方法呢?

主要有下面兩種方式
1、訂閱React Navigation事件
2、使用 withNavigationFocus HOC或者 <NavigationEvents />組件。

<NavigationEvents />組件為例:

假定這樣一個場景:導航控制器的根視圖控制器是HomeScreen,點擊HomeScreen上面的按鈕跳轉到下一個控制器。那么我們要監聽HomeScreen的四個事件。
代碼如下:

class HomeScreen extends  Component{

    render() {
    return (
        <View style={styles.container}>
            <NavigationEvents
                onWillFocus={payload => console.log('will focus',payload)}
                onDidFocus={payload => console.log('did focus',payload)}
                onWillBlur={payload => console.log('will blur',payload)}
                onDidBlur={payload => console.log('did blur',payload)}
            />
            <Text>Home頁面</Text>
            <Button
                title={"Go to Login"}
                onPress={()=> {
                  this.props.navigation.navigate('Details');
                }}
            />
        </View>
    )
  }
}

打印如下:


監聽React Navigation事件

傳遞參數給路由

一、參數傳遞

1、使用this.props.navigation.navigate('RouteName', { /* params go here */ })
方法的第二個參數傳遞給路由

2、推薦傳遞的參數是 JSON序列化的

 this.props.navigation.navigate('Details', {
              itemId: 86,
              otherParam: 'anything you want here',
            });
二、獲取參數

1、直接使用this.props.navigation.state.params進行訪問,如果沒有對應參數,則可能是null。
2、使用his.props.navigation.getParam進行訪問

const { navigation } = this.props;
//第二個參數是設置默認值
const itemId = navigation.getParam('itemId', 'NO-ID');

3、如果你想通過 prop 直接訪問 params(例如: this.props.itemId)而不是this.props.navigation.getParam,您可以使用社區開發的 react-navigation-props-mapper軟件包。

4、官方Demo

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

class DetailsScreen extends React.Component {
  render() {
    /* 2. Get the param, provide a fallback value if not available */
    const { navigation } = this.props;
    const itemId = navigation.getParam('itemId', 'NO-ID');
    const otherParam = navigation.getParam('otherParam', 'some default     value');

    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Details Screen</Text>
        <Text>itemId: {JSON.stringify(itemId)}</Text>
        <Text>otherParam: {JSON.stringify(otherParam)}</Text>
        <Button
          title="Go to Details... again"
          onPress={() =>
            this.props.navigation.push('Details', {
              itemId: Math.floor(Math.random() * 100),
            })}
        />
        <Button
          title="Go to Home"
          onPress={() => this.props.navigation.navigate('Home')}
        />
        <Button
          title="Go back"
          onPress={() => this.props.navigation.goBack()}
        />
      </View>
    );
  }
}

配置標題欄

一、設置不含參數的標題欄

1、每個頁面組件可以有一個名為navigationOptions的靜態屬性,用于設置標題欄的標題使用的是title屬性

class HomeScreen extends React.Component {
 static navigationOptions = {
    title: 'Home',
  };

  /* render function, etc */
}
二、設置含參數的標題欄

我們將navigationOptions作為一個函數,react會使用包含{ navigation, navigationOptions, screenProps }這些屬性的對象調用它,由于傳值只用到navigation,所以代碼如下:

class DetailsScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    return {
      title: navigation.getParam('otherParam', 'A Nested Details Screen'),
    };
  };

 /* render function, etc */
}

對上面提到的三個屬性,解釋如下:

  • navigation - 頁面的 導航屬性 ,在頁面中的路由為navigation.state
  • screenProps - 從導航器組件上層傳遞的 props
  • navigationOptions - 如果未提供新值,將使用的默認或上一個選項(這里說的默認值就是指共享的navigationOptions)
三、更新含參數的標題欄

通常有必要從已加載的頁面組件本身更新當前頁面的navigationOptions配置。 我們可以使用this.props.navigation.setParams

  /* Inside of render() */
  <Button
    title="Update the title"
    onPress={() => this.props.navigation.setParams({otherParam: 'Updated!'})}
  />
四、調整標題樣式

定制標題樣式時有三個關鍵屬性:headerStyle、headerTintColor和headerTitleStyle。

  • headerStyle:一個應用于 header 的最外層 View 的 樣式對象, 如果你設置 backgroundColor ,他就是header 的顏色。

  • headerTintColor:返回按鈕和標題都使用這個屬性作為它們的顏色。 在下面的例子中,我們將 tint color 設置為白色(#fff),所以返回按鈕和標題欄標題將變為白色。

  • headerTitleStyle:如果我們想為標題定制fontFamily,fontWeight和其他Text樣式屬性,我們可以用它來完成。

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
    headerStyle: {
      backgroundColor: '#f4511e',
    },
    headerTintColor: '#fff',
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  };

 /* render function, etc */
}
五、跨頁面共享通用的navigationOptions

如果我們要統一配置標題欄的樣式,我們只需把這些代碼移動到創建導航器的代碼中,放在defaultNavigationOptions屬性下

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Home',
    /* No more header config here! */
  };

  /* render function, etc */
}

    /* other code... */

const RootStack = createStackNavigator(
  {
    Home: HomeScreen,
    Details: DetailsScreen,
  },
  {
    initialRouteName: 'Home',
    /* The header config from HomeScreen is now here */
    defaultNavigationOptions: {
      headerStyle: {
        backgroundColor: '#f4511e',
      },
      headerTintColor: '#fff',
      headerTitleStyle: {
        fontWeight: 'bold',
      },
    },
  }
);
六、覆蓋共享的navigationOptions

如果在頁面組件上面也指定了navigationOptions,那么將會覆蓋父級的navigationOptions。

七、使用自定義組件替換標題
class LogoTitle extends React.Component {
  render() {
    return (
      <Image
        source={require('./spiro.png')}
        style={{ width: 30, height: 30 }}
      />
    );
  }
}

class HomeScreen extends React.Component {
  static navigationOptions = {
    // headerTitle instead of title
    headerTitle: <LogoTitle />,
   };

  /* render function, etc */
}

標題欄按鈕

1、向標題欄中添加一個按鈕
class HomeScreen extends React.Component {
  static navigationOptions = {
    headerTitle: <LogoTitle />,
    headerRight: (
      <Button
       onPress={() => alert('This is a button!')}
        title="Info"
        color="#fff"
      />
    ),
  };
}

在navigationOptions中this綁定的不是 HomeScreen 實例,所以你不能調用setState方法和其上的任何實例方法。

2、標題欄和其所屬的頁面之間的交互

由于上面提到的,在navigationOptions中不能使用this,所以使用getParamsetParam實現

class HomeScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    return {
      headerTitle: <LogoTitle />,
      headerRight: (
        <Button
          onPress={navigation.getParam('increaseCount')}
          title="+1"
          color="#fff"
        />
      ),
    };
  };

  componentDidMount() {
    this.props.navigation.setParams({ increaseCount: this._increaseCount });
  }

  state = {
    count: 0,
  };

  _increaseCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  /* later in the render function we display the count */
}

下面分析一下這段代碼:
1、首先按鈕的點擊事件我們應該傳遞一個函數 或者null
所以在componentDidMount方法中將參數賦值為一個函數
此時再次點擊按鈕的時候就會調用這個函數了。

2、React Navigation不保證屏幕組件在標題之前安裝,此時按鈕點擊事件相當于null,不會造成異常。

Tab Navigation

在iOS開發中,最常見的UI框架是Tab中包含若干Navigation。相應的路由配置如下:

import Main from './Component/Main.js';
import Home from './Component/Home.js';
import Find from './Component/Find.js';
import Message from './Component/Message.js';
import Mine from './Component/Mine.js';

//創建navigation
const MainStack = createStackNavigator({
    main:{
        screen:Main,
    }
})

const HomeStack = createStackNavigator({
    home:{
        screen:Home,
    },
})

const FindStack = createStackNavigator({
    find:{
        screen:Find,
    }
})

const MessageStack = createStackNavigator({
    home:{
        screen:Message,
    }
})

const MineStack = createStackNavigator({
    home:{
        screen:Mine,
    }
})

//配置Tab的路由
const TabNav = createBottomTabNavigator({
    mainStack:{
        screen:MainStack,
    },
    homeStack:{
        screen:HomeStack,
    },
    findStack:{
        screen:FindStack,
    },
    messageStack:{
        screen:MessageStack,
    },
    mineStack:{
        screen:MineStack,
    }
})

const AppContainer = createAppContainer(TabNav);

在實際開發中,我們需要自定義Tab上面的樣式,類似于createStackNavigator,我們可以用相同的方法配置navigationOptions。

在我們配置樣式時,存在一個注意點。當我們同時使用了createStackNavigatorcreateBottomTabNavigator時,在配置樣式時:如果我們想設置Navigation的樣式,我們可以在createStackNavigator或者Navigator的頁面元素中(就是以導航控制器為容器的那些子控制器)設置。如果我們想配置Tab的樣式,我們可以在createBottomTabNavigator或者navigator(就是以Tab為容器的那些導航控制器)實例中設置。

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

推薦閱讀更多精彩內容