react-navigation使用技巧(再進階)

如果在學習react-native的過程中遇到什么問題,歡迎加入QQ群397885169一起學習,一起成長。

本文是基于最新的react-navigation^3.x來書寫的。

以下15條,在我開源的識兔中,都是可以找到實例的,歡迎參考,歡迎star

前言

關于react-navigation的文章,這已經是第三篇了,這個庫從最初的beta版到最新的2.x版本,更新頻率是很快的,這個庫也越來越完善,很多1.x的技巧已經完全不適用于新的版本,然而群里每天又有很多人再問,為了解(jiao)決(yu)這個問題,所以,動手寫了這篇文章。

更新版本到react-navigation3.x

因為react-navigation版本更新啦,本文又一次更新啦!

文章鏈接

react-navigation使用技巧 : 適合初學者,基本講解了2.x的api,3.x待更新
react-navigation使用技巧(進階篇) : 適合遇到某些問些不知如何解決的人,廉頗老矣,尚能飯否。
react-navigation使用技巧(再進階) : 本篇文章,適合對于新版本(2.x,3.x)有疑問的人,對新的Api做了講解

問題&技巧

1. 安裝react-navigation3.x報錯

因為在新版本中,新增了一個原生庫react-native-gesture-handler,所以,不管是升級還是新安裝,都需要安裝這個庫,如果已經安裝過了,請無視。

安裝并link過后,能解決百分之50的問題。下一個解決另一半問題

yarn add react-native-gesture-handler
react-native link react-native-gesture-handler

2. 安裝3.x后,需要將最外層的包裹形式修改為createAppContainer

在之前的版本中,使用createStackNavigator后,就會自動實現createAppContainer,但在新版本中,需要手動使用createAppContainer來包裹最外層的路由。

識兔代碼 為例

export const AuthLoadingRouter = createAppContainer(
  createSwitchNavigator(
    {
      AuthLoading: AuthLoadingScreen,
      AppRouter: AppRouter,
      AuthRouter: AuthRouter
    },
    {
      initialRouteName: 'AuthLoading'
    }
  )
);

createAppContainer 提供了兩個方法來使用

onNavigationStateChange: 每次導航器管理的導航狀態發生變化時調用的函數。它接收之前的狀態,新的狀態和發出狀態變化的行為。默認情況下,它會將狀態更改打印到控制臺。

uriPrefix:深度鏈接,處理深層鏈接路徑時的方法

3. 模態動畫

作為一個前iOS開發者,從react-navigationbeta版開始,就一直再等待類似iOSpresent動畫效果,雖然,可以使用mode: modal來實現效果,但這個是全局的,使用這個方法后,所有的頁面跳轉都是modal效果,這是不能容忍的。
1.x之前,我試過用拆分StackNavigator的方式實現了效果,但代碼不忍直視。

3.x中,終于可以手動配置,讓某些路由實現想要的動畫了。

還是以識兔代碼為例,我將跳轉登錄的代碼,改成了Modal動畫。


import { createBottomTabNavigator, createStackNavigator, StackViewTransitionConfigs } from 'react-navigation';

// 數組中的路由,可以自定義動畫效果,這里我只改了登錄
const IOS_MODAL_ROUTES = ['Login'];

const dynamicModalTransition = (transitionProps, prevTransitionProps) => {
  const isModal = IOS_MODAL_ROUTES.some(
    screenName =>
      screenName === transitionProps.scene.route.routeName ||
      (prevTransitionProps && screenName === prevTransitionProps.scene.route.routeName)
  )
  return StackViewTransitionConfigs.defaultTransitionConfig(
    transitionProps,
    prevTransitionProps,
    isModal
  );
};

const HomeStack = createStackNavigator({
    MyTab: {
      screen: MyTab
    }, 
    BuDeJie: {
      screen: BuDeJie
    }, 
    Login: {
      screen: Login
    } 
},{ 
    initialRouteName: 'MyTab', 
    transitionConfig: dynamicModalTransition
});


4. 點擊Tab,滾動到頁面頂部

這個也是3.x版本中,新增的東西,react-navigation導出了ScrollViewFlatListSectionList

導出的三個組件中,方法和原來的一樣,只不過在內部實現了,滾動到頂部的方法。

issues上有很多bug哦,慎用。

5. 新增defaultNavigationOptions

之前初始化配置路由屬性都是在navigationOptions中,這樣雖然更便捷,但如果想在頁面中修改卻不行,不會覆蓋初始值,在新版本中提供了defaultNavigationOptions,用法和之前一樣,但終于可以在頁面中覆蓋初值了

6. 為什么無法修改跟路由的導航頭,我想修改它的顏色,隱藏等等

因為在react-navigation2.x版本中,作者將該庫的路由包裹方式改了,之前TabNavigator中是包含了StackNavigator大部分屬性的,所以,可以很簡單的設置headerheaderStyleheaderTitle等屬性的。
新版本中,createBottomTabNavigator沒有了這些屬性,如果想要修改這些屬性,有兩種方式:

  1. createStackNavigator初始化頁面,然后再用createBottomTabNavigator包裹再外層,最外層再用createStackNavigator包裹一遍,用來跳轉其他子頁面。
const ShiTuStack = createStackNavigator({
  ShiTu: ShiTu,
});

const BuDeJieStack = createStackNavigator({
  BuDeJie: BuDeJie,
  BuDeJieDetail: BuDeJieDetail,
});

const MyTab = createBottomTabNavigator({
  Tabs: ShiTuStack,
  Details: BuDeJieStack,

});

const AppRouter = createStackNavigator({
  Auth: AuthScreen,
  MyTab: MyTab,
});
  1. 使用setParams屬性,在根路由頁面的componentDidMount中調用
this.props.navigation.setParams({title: '識兔'});

接下來在路由頁面中

export const AppRouter = createStackNavigator({
    MyTab: {
        screen: MyTab,
    },
    BuDeJie: {
        screen: BuDeJie,
    },
}, {
       navigationOptions: ({navigation}) => NavigatorOptions(navigation)
}
const NavigatorOptions = (navigation) => {
    const routes = navigation.state.routes;
    // 通過params得到傳進來的title,并賦值給headerTitle。
    const params = routes ? routes[navigation.state.index].params : null;
    const headerTitle = params ? params.title : '';
    const headerTitleStyle = {
        fontSize: System.iOS ? 23 : 20,
        color: 'white',
        flex: 1,
        textAlign: 'center',
        paddingTop: System.Android ? 17 : null,
    };
    const headerBackTitle = null;
    const headerTintColor = 'white';
    const headerStyle = {
        backgroundColor: Theme.navColor,
        shadowColor: 'transparent',
        shadowOpacity: 0,
        borderBottomWidth: 0,
        borderBottomColor: 'transparent',
        elevation: 0,
    };
    const header = null;
    return { headerTitle, headerStyle, headerTitleStyle, headerBackTitle, headerTintColor, header };
};

以上的兩種方式,并不是很好的方式,react-navigation導航條的自定義性雖然越來越強了,但某些情況下還是沒有完全自定義的導航更好控制,比如說我想監聽到react-navigation自帶導航的返回按鈕,只能去頁面中復寫headerLeft屬性,這樣就不如,完全控制了。

我在識兔中,提供了一套基于teaset的導航 + 適配頁面,歡迎使用哦!

7. 安卓實現類似iOS的push動畫

這個是老生常談的問題了。我之前更新的文章中,有1.x版本和2.10.x版本之前的實現方式,但2.13.0又改了,這里把三種方式都整理出來。

三種的用法是一樣的,只不過,引入文件的路徑有修改。先把用法發出來。

{
    // 快速定制導航條,新版識兔中所有的導航都是重寫的,所以這里會將全部的導航置空
    navigationOptions: () => ({ 
        header: null,   
        gesturesEnabled: true,  
    }),
    transitionConfig: () => ({
        screenInterpolator: StackViewStyleInterpolator.forHorizontal,
    })
}

官方一共提供了四種動畫方式

從右向左: forHorizontal iOS默認效果
從下向上: forVertical
安卓那種的從下向上: forFadeFromBottomAndroid
無動畫: forInitial

如果想自定義的話,可以使用官方推薦的三方庫FluidTransitions

3.11.0

import StackViewStyleInterpolator from 'react-navigation-stack/src/views/StackView/StackViewStyleInterpolator';

2.13.0

import StackViewStyleInterpolator from
 'react-navigation-stack/dist/views/StackView/StackViewStyleInterpolator';

2.x

import StackViewStyleInterpolator from 
'react-navigation/src/views/StackView/StackViewStyleInterpolator';

1.x

import CardStackStyleInterpolator from
 'react-navigation/src/views/CardStack/CardStackStyleInterpolator';

8. 讓TabBar擁有點擊事件

react-navigation最初的版本是沒有這個事件的,那個時候,我手寫了這個事件并暴露出去,后來官方添加了這個事件,只不過1.x2.x的返回屬性不一樣,但方法名是一樣的。

2.x

tabBarOnPress: async (obj: any) => {
    console.log(obj);
    try {
        const userData = await AsyncStorage.getItem('USER_INFO');
        if (userData) {
            obj.defaultHandler();
        }
        else {
            obj.navigation.navigate('Login');
        }
    } catch (e) {
        Toast.show(e.message, 'center', 1000);
    }
}

1.x

tabBarOnPress:(obj)=>{
    console.log(obj);
    obj.jumpToIndex(obj.scene.index)
}

9. 重復跳轉同一個頁面

晴明大神指點,將該方法更新

重復跳轉是可以用過push跳轉的,但容易發生的問題就是可能會導致重復跳轉該頁面,而用navigate使通過key控制的,所以,基本可以保證不會重復跳轉,而直接使用this.props.navigate.navigate('Detail')是不可行的,需要手動設置key,作為唯一標識。

this.props.navigation.navigate({
    key: user.id,
    routeName: 'Detail'
})

想去同一個頁面可以用navigate,但新版本中,這么做卻不行了,因為navigate是根據key查找頁面的,如果頁面入棧就不跳轉。 這里要使用不算新的apipush咯,才能實現重復跳轉。

10. 保持頁面狀態

在開發RN的過程中,經常會遇到,我在開發一個頁面,reload之后,又要重新進去,在2.x版本中,新增了一個實驗性的apipersistenceKey,它會自動保存當前頁面的路由,并在reload之后默認打開該頁面。

以識兔中路由層index.js為例

const navigationPersistenceKey = __DEV__ ? 'NavigationStateDEV' : null;

<AuthLoadingRouter persistenceKey={navigationPersistenceKey}
                   renderLoadingExperimental={() => <ActivityIndicator size='large' color='black' />}
                    />

persistenceKey就是保持當前頁面的key,通過存入AsyncStorage,在下次進入頁面后,通過讀取這個key來打開相應的頁面。

renderLoadingExperimental因為AsyncStorage是異步加載的,所以在取值過程中可能出現閃白的情況,可以使用這個屬性,呈現加載視圖

注:以上方式是實驗性方法,可能在未來的版本中有變更

11. 頁面的生命周期

如果開發過原生都會知道,原生中每個頁面都是有獨立的生命周期的,以iOS為例。

  • viewWillAppear: 控制器的view將要顯示

  • viewDidLoad:view加載完畢

  • viewWillDisappear:控制器的view即將消失的時候

  • viewDidAppear:控制器的view完全顯示

從開發RN第一天開始,就很期待有這些生命周期,但React能用的頁面生命周期很少,常用的有

  • componentWillMount(快被廢棄了):頁面將要顯示

  • componentDidMount:頁面已經顯示

  • componentWillUnmount:頁面將要消失

react-navigation中終于實現了這個心愿,為頁面添加了可用的生命周期。

  • onWillBlur:頁面將要失去焦點

  • onDidBlur:頁面已經失去焦點

  • onWillFocus:頁面將要獲得焦點

  • onDidFocus:頁面已經獲得焦點

react-navigation提供了兩種方式獲取這個生命周期

手動監聽

componentDidMount() {
    // 通過addListener開啟監聽,可以使用上面的四個屬性
    this._didBlurSubscription = this.props.navigation.addListener(
        'didBlur',
        payload => {
            console.debug('didBlur', payload);
        }
    );
}
componentWillUnmount() {
    // 在頁面消失的時候,取消監聽
    this._didBlurSubscription && this._didBlurSubscription.remove();
}

通過組件方法監聽

下面這種方式,會自動處理取消監聽

<NavigationEvents 
    onWillFocus={onWillFocus}
    onDidFocus={onDidFocus}
    onWillBlur={onWillBlur}
    onDidBlur={onDidBlur}   
/>

12. 處理安卓返回鍵

介紹完生命周期之后,之前存在的各種問題都迎刃而解了,只要活用這幾個生命周期,能完成很多麻煩的問題,比如說安卓的返回鍵,之前的處理方式是在componentDidMount訂閱事件,在componentWillUnmount取消事件,但是componentWillUnmount只有在頁面銷毀的時候才會觸發,這樣就導致,很多時候要把返回事件寫在很多個頁面,分別監聽和銷毀,有了聲明周期這個就簡單了。

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)
    );
  }

  onBackButtonPressAndroid = () => {
    if (this.isSelectionModeEnabled()) {
      this.disableSelectionMode();
      return true;
    } else {
      return false;
    }
  };

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

  render() {
    // ...
  }

13. iPhoneX適配和安卓異形屏適配

react-navigation中也提供了SafeAreaView這個組件,它會自動處理頂部導航欄和底部標簽欄的適配,并且也會自動適配安卓的異形屏。用法和react-native提供的也類似。

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>
    );
  }

14. 在任何頁面或者組件中都得到navigation對象

在很多情況下,我們的組件或者頁面需要得到this.props.navigation對象,但是會報錯,說沒有找到navigation對象,為了解決這個問題,在react-navigation中提供了一個高階組件withNavigation來應對。

注:頁面中沒有navigation對象,一般都是沒有在初始化路由的時候注冊該頁面,所以請先檢查自己的寫法,然后再使用該組件
注:該組件最好的使用場景,應該是組件中,比如說返回按鈕,或者跳轉按鈕等等

在這里提供識兔中返回按鈕的代碼

import { withNavigation } from 'react-navigation';

class NavigatorBar extends React.PureComponent<Props> {
    backButtonPress = () => {
        const {backButtonPress} = this.props;
        if (backButtonPress) {
            backButtonPress();
        } else {
            this.props.navigation.goBack();
        }
    }
    
    renderLeftView = () => {
        const {isTopNavigator, leftView, } = this.props;
        let left;
        if (isTopNavigator || leftView) {
            left = leftView;
        } else {
            left = <NavigationBar.BackButton title='返回' onPress={this.backButtonPress}/>;
        }
        return left;
    }
    
    render() {
        return (
            <NavigationBar leftView={this.renderLeftView()}                         
                           titleStyle={{fontSize: System.iOS ? 23 : 20, color: 'white', fontWeight: 'bold'}}
                           {...this.props}
            />
        );
    }
}
export default withNavigation(NavigatorBar);

15. 隱藏標簽欄

正常情況下,都會用createStackNavigator包裹createBottomTabNavigator,但就是存在不正常的情況呢? 那應該怎么處理tab的隱藏顯示呢?其實很簡單

const AppRouter = createStackNavigator({
  MyTab: MyTab,
  BuDeJie: BuDeJie,
});

BuDeJie.navigationOptions = ({ navigation }) => {
  let tabBarVisible = true;
  if (navigation.state.index > 0) {
    tabBarVisible = false;
  }
  return {
    tabBarVisible,
  };
};

總結

以上是我整理的15條關于新版react-navigation的進階教程,如果還需要什么新的教程,歡迎加入QQ群397885169一起學習,一起成長。

react-navigation3.x版本,強烈推薦更新,雖然還是有一些痛點,但可以看到這個庫還是在不斷完善的,相信它和RN會越來越好。

以上10條,在我開源的識兔中,都是可以找到實例的,歡迎參考,歡迎star

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容