标签:redux cti closed 创建 事件监听 res reset 控制 form
React Navigation: Android 和 iOS 设备上的路由工具,包括手势和动画。
在使用 react-navigation 之前,我们需要创建一个 react-native 项目。(参考https://reactnative.cn/docs/getting-started)
在 web 项目中的 react-router,只负责功能实现,样式是需要开发者自己去设计的。而 react-navigation 自带了几种常见的交互和样式。它共有四种常用的 Navigator:
Stack的功能与 react-router 类似,但是每一个页面有一个标题栏。
Switch(Switch / AnimatedSwitch) 没有样式,为鉴权场景而生。它每次只渲染一个页面,不处理返回操作,并在你切换时将路由重置为默认状态。
Drawer菜单被放在一个抽屉中,通过一个在屏幕最左边的右滑手势,来打开抽屉。
Tab(BottomTab / MaterialBottomTab / MaterialTopTab) 菜单被放在 Tabs 中,可以在屏幕的顶部或底部。
创建这些导航的语法都是类似的:
1 import { createAppContainer, createSwitchNavigator } from ‘react-navigation‘; 2 import { createDrawerNavigator } from ‘react-navigation-drawer‘; 3 import { createStackNavigator } from ‘react-navigation-stack‘; 4 import { createMaterialTopTabNavigator } from ‘react-navigation-tabs‘; 5 6 const navigator = create***Navigator( 7 // routes 8 { 9 Home: { // 如果没有 navigation 等其他选项,也可以简写为:Home: Home 10 screen: Home // 加载的组件 11 navigationOptions: {}, // screen 配置 12 path: ‘people/:name‘, // deep-link 或者 web应用 场景下使用 13 } 14 }, 15 // configs 16 { 17 initialRouteName: ‘‘, // 初始路由 18 navigationOptions: {}, // navigator 的配置 19 defaultNavigationOptions: {}, // screens 的配置 20 paths: {} // deep-link 场景 21 ... 22 } 23 ) 24 export default createAppContainer(navigator)
navigationOptions可以写在 route 中,可以写在 navigator 中(3.x 开始叫defaultNavigationOptions),也可以写在 screen 中。优先级是 route > screen > navigator。
1 ({ navigation, screenProps, navigationOptions }) => ({ // object | function 2 title: ‘标题‘, // ??默认情况下按照平台惯例设置,所以在 iOS 上标题居中,在 Android 上左对齐 3 headerTitle: <Title />, // 也可以设置一个组件,它可以通过 nativation.getParam、setParams 和页面通信,也可以使用 redux 等 4 headerRight: <Title />, 5 headerLeft: <Title />, // 会覆盖返回按钮 6 headerStyle: {}, // 整个标题栏 7 headerTintColor: ‘‘, // 标题和返回按钮的颜色 8 headerTitleStyle: {}, // 标题的样式 9 })
createAppContainer将导航配置转变成 React 组件,这时它就可以放在项目的任何地方了。生成的组件可以接受两个属性:onNavigationStateChange和uriPrefix
1 const AppContainer = createAppContainer(navigator); 2 3 <AppContainer 4 onNavigationStateChange={(prevState, newState, action) => {}} // 监听所有的路由状态变化 5 uriPrefix="/app" // deep-link 场景 6 />
navigate
下图说明 stackNavigator 中的 navigate 行为。当栈内没有找到该路由对应的页面时,就推入一个新的页面,否则只是弹出到已有页面。
Drawer、Tab 中,一个路由只能有一个组件存在——底层也是 stack 实现,但 this.props.navigation.state 永远都是所有路由的集合。
goBack
此图说明 stackNavigator 中的 goBack 行为,传入参数表示「以我为参考进行回退」
Drawer、Tab 中,goBack 默认返回初始路由。
push,推入页面(和 navigate 的区别是,push不会去查找栈中是否已经有该路由)
pop,弹出页面
popToTop,弹出到底部路页面
replace,替换
reset,重置当前 navigator
dismiss,退出当前 navigator,返回上层 navigator
openDrawer
closeDrawer
toggleDrawer控制菜单显隐
state
setParams(name, value)
getParams(name, defaultValue)
isfocused() // 是否被聚焦
dangerouslyGetParent() // 获取父导航
dispatch() // 用 props.navigation.dispatch(action) 的方式去改变路由,如下图
addListener(eventName, ({ action, context, lastState, state, type }) => {})
上面的这些属性都是在 screen 组件中,通过this.props.navigation调用的。这就意味着,如果有深层次的子组件想操作路由,screen 就需要将navigation作为子组件的属性传递下去。以下提供了一些不传属性也能操作路由的方法:
这是一个高阶组件,对内传递给子组件navigation属性,对外暴露onRef属性传递出子组件的引用
1 import React from ‘react‘; 2 import { Button } from ‘react-native‘; 3 import { withNavigation } from ‘react-navigation‘; 4 5 class MyBackButton extends React.Component { 6 render() { 7 return ( 8 <Button 9 title="Back" 10 onPress={() => { 11 this.props.navigation.goBack(); 12 }} 13 /> 14 ); 15 } 16 } 17 export default withNavigation(MyBackButton); 18 19 // 使用 20 <MyBackButton onRef={elem => (this.backButton = elem)} />;
也是一个高阶组件,对内传递给子组件isFocused属性。注意??,由于是属性传递,会导致组件重新渲染,需要shouldComponentUpdate来控制组件渲染次数。
1 import React from ‘react‘; 2 import { Text } from ‘react-native‘; 3 import { withNavigationFocus } from ‘react-navigation‘; 4 5 class FocusStateLabel extends React.Component { 6 render() { 7 return <Text>{this.props.isFocused ? ‘Focused‘ : ‘Not focused‘}</Text>; 8 } 9 } 10 11 export default withNavigationFocus(FocusStateLabel);
还有一种办法就是将某个 navigator 保存为全局变量,这样不同层级的页面也可以方便地互相导航。
1 import { NavigationActions } from ‘react-navigation‘; 2 3 let _root; 4 const setTopLevelNavigator = (navigatorRef) => { 5 _root = navigatorRef; 6 } 7 const getTopLevelNavigator = () => { 8 return _root 9 } 10 export default { 11 setTopLevelNavigator, 12 getTopLevelNavigator 13 }; 14 const App = () => { 15 return ( 16 <RootNavigator ref={navigation.setTopLevelNavigator} /> 17 ); 18 }; 19 const navigator = navigation.getTopLevelNavigator(); 20 navigator.dispatch(NavigationActions.navigate({ 21 routeName: ‘Drawer‘, 22 action: DrawerActions.openDrawer() 23 }))
首先,我们有一个广告页、登录页、主页的选择的场景,这三个页面是互斥的,只会存在一个,这种场景就适合用 SwitchNavigator。
顺风车、出租车明显是 TopTabNavigator 的交互——注意它们的上方还有一个类似标题栏的东西,这意味着可以在外面再套一层 StackNavigator(订单页也是如此)。
而这一层 StackNavigator 和订单页的 StackNavigator 都是属于 DrawerNavigator 的内容,于是我们就有了下图这样一个路由的结构。
使用 react-native 的ScrollView/FlatList/SectionList的时候,有一个非常方便的交互设计:点击手机顶部的时候可以快速滚到顶部初始位置。如果想要点击 TabNavigator 的 Tab 时,也想有这种效果怎么办?可以直接使用 react-navigation 封装过的 ScrollView/FlatList/SectionList。?
react-native 的SafeAreaView大家都知道,可以让手机在 ios 的刘海屏/美人尖等异型屏上能正常显示。
react-navigation 提供的 SafeAreaView 则多了一个属性forceInset,可以让我们更加精细地控制四边的padding。它在 top | bottom | left | right | vertical | horizontal 几种方向上有两种值可以设置:‘always‘ 和 ‘nerver‘。
这里要注意的是,如果 SafeAreaView是包裹在页面上的,不包括导航栏的高度,如下图左红色部分。如果 SafeAreaView 是包裹在 RootNavigator 上的,就包括导航栏的高度,如下图右蓝色部分。当然就算我们只放在页面上,导航栏的高度也对异性屏做了兼容,使得我们的页面在ios各种机型上正常显示(react-navigation 4.x)。
那么,Android 异型屏怎么办?借助 react-native-device-info 识别是否有 notch,然后设置 SafeAreaView 的高度
1 import { Platform } from ‘react-native‘; 2 import SafeAreaView from ‘react-native-safe-area-view‘; 3 import DeviceInfo from ‘react-native-device-info‘; 4 5 if (Platform.OS === ‘android‘ && DeviceInfo.hasNotch()) { 6 SafeAreaView 7 .setStatusBarHeight 8 /* Some value for status bar height + notch height */ 9 (); 10 }
NavigiationEvents 是 react-navigation 导出的一个组件,它上面有五个属性。在任何组件上都可以放置<NavigiationEvents />,路由事件会自顶向下传导,父组件、子组件的事件处理函数会依次被触发。
onWillFocus
onDidFocus
onWillBlur
onDidBlur
navigator(默认当前所处上下文)
1 import React from ‘react‘; 2 import { View } from ‘react-native‘; 3 import { NavigationEvents } from ‘react-navigation‘; 4 5 const MyScreen = () => ( 6 <View> 7 <NavigationEvents 8 onWillFocus={payload => console.log(‘will focus‘, payload)} 9 onDidFocus={payload => console.log(‘did focus‘, payload)} 10 onWillBlur={payload => console.log(‘will blur‘, payload)} 11 onDidBlur={payload => console.log(‘did blur‘, payload)} 12 /> 13 {/* 14 Your view code 15 */} 16 </View> 17 ); 18 19 export default MyScreen;
TypeScript 支持
https://reactnavigation.org/docs/en/typescript.html
2.14.0 之前的版本使用 react-native-screens 来进行 native 侧的性能优化
https://reactnavigation.org/docs/en/react-native-screens.html
状态保持(实验性)
涉及到的 API: persistNavigationState 、 loadNavigationState
1 const AppNavigator = createStackNavigator({ }) 2 const persistNavigationState = async (navState) => { 3 try { 4 await AsyncStorage.setItem(‘myNavigator‘, JSON.stringigy(navState)) 5 } catch (e) { 6 } 7 } 8 const loadNavigationState = async() => { 9 const jsonString = await AsynStorage.getItem(‘myNavigator‘) 10 return JSON.parse(jsonString) 11 } 12 const App = () => <AppContainer 13 persistNavigationState={persistNavigationState} 14 loadNavigationState={loadNavigationState} 15 />
此功能在开发模式下特别有用。你可以使用以下方法,有选择地启用它:
1 const AppContainer = createStackNavigator({ }) 2 function getPersistenceFunctions () { 3 return __DEV__ ? { 4 persistNavigationState, 5 loadNavigationState 6 } : undefined 7 } 8 const App = () => <AppContainer {...getPersistenceFunctions()} />
由于状态是异步加载的,你可以在 AppContainer 中使用属性renderLoadingExperimental渲染一个空页面
自定义Android返回键行为
默认情况下,当用户按下Android 物理返回键时,reat-navigation会返回到上一个页面,如果没有可返回的页面,则退出应用。
自定义行为需要使用 react-native 的BackHandler这个API
1 import { BackHander } from ‘react-native‘; 2 constructor() { 3 this._didFocusSubscription = props.navigation.addListener( 4 ‘didFocus‘, 5 payload => BackHandler.addEventListener( 6 ‘hardeareBackPress‘, 7 this.onBackButtonPressAndroid, // 返回 true 则表示我们已经处理了该事件,并且react-navigation 的事件监听器不会被调用,因此不会销毁当前页。 返回false会该方法继续执行 - react-navigation 的事件监听器将销毁当前页面。 8 ) 9 ) 10 } 11 componentDidMount() { 12 this._willBlurSubscription = this.props.navigation.addListener( 13 ‘willBlur‘, 14 payload => 15 BackHandler.removeEventListener( 16 ‘hardwareBackPress‘, 17 this.onBackButtonPressAndroid 18 ) 19 ); 20 } 21 componentWillUnmount() { 22 this._didFocusSubscription && this._didFocusSubscription.remove(); 23 this._willBlurSubscription && this._willBlurSubscription.remove(); 24 }
参考:https://reactnavigation.org/
标签:redux cti closed 创建 事件监听 res reset 控制 form
原文地址:https://www.cnblogs.com/ppJuan/p/12207335.html