如果在學習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-navigation
beta版開始,就一直再等待類似iOS
的present
動畫效果,雖然,可以使用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
導出了ScrollView
,FlatList
和SectionList
。
導出的三個組件中,方法和原來的一樣,只不過在內部實現了,滾動到頂部的方法。
看issues
上有很多bug哦,慎用。
5. 新增defaultNavigationOptions
之前初始化配置路由屬性都是在navigationOptions
中,這樣雖然更便捷,但如果想在頁面中修改卻不行,不會覆蓋初始值,在新版本中提供了defaultNavigationOptions
,用法和之前一樣,但終于可以在頁面中覆蓋初值了
6. 為什么無法修改跟路由的導航頭,我想修改它的顏色,隱藏等等
因為在react-navigation2.x
版本中,作者將該庫的路由包裹方式改了,之前TabNavigator
中是包含了StackNavigator
大部分屬性的,所以,可以很簡單的設置header
,headerStyle
,headerTitle
等屬性的。
新版本中,createBottomTabNavigator
沒有了這些屬性,如果想要修改這些屬性,有兩種方式:
- 用
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,
});
- 使用
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.x
和2.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