tstat

react-navigation笔记

安装依赖

npm install [email protected] [email protected] [email protected] [email protected] @react-native-community/[email protected] @react-navigation/[email protected] 
@react-navigation/[email protected] @react-navigation/[email protected]

在根文件顶部引入,否则应用会崩溃

import 'react-native-gesture-handler';

将整个应用包装在 NavigationContainer

import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    {/* Rest of your app code */}
  );
}

安装 @react-navigation/stack

npm install @react-navigation/stack

createStackNavigator 是一个返回一个对象(包含2个属性 ScreenNavigator )的函数。 Navigator 应该包含子元素 Screen ,来定义路由配置。用 NavigationContainer 包裹 Navigator

// In App.js in a new project

import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen() {
  return (
    
      Home Screen
    
  );
}

function DetailsScreen() {
  return (
    
      Details Screen
    
  );
}

const Stack = createStackNavigator();

function App() {
  return (
    
      
        
        
      
    
  );
}

export default App;

配置

Screen 的必需配置属性是 namecomponent 。在 Navigator 上配置 initialRouteName

component 属性接受组件,不接受一个render函数。不要传一个内联函数例如 component={() => } ,否则当父组件re-render时,你的组件将卸载和重载失去的所有state。

每个路由的配置可以放在 options 里面。相同的可以配在 NavigatorscreenOptions 里面。

传递额外的props:

  • (推荐)使用React Context,用context provider包裹navigator去传数据到screens。
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      
        
      
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    
); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return
  • 用render callback而不是定义一个component prop。默认情况下react-navigation底层做了优化防止重复渲染,但是使用render callback则会移除这些优化。如果用render callback,需要确保Screen组件使用了 React.memoReact.PureComponent

  {props => }

路由跳转

import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen({ navigation }) {
  return (
    
      Home Screen
      
  • navigation 属性会传递给每个在stack navigator中定义的screen component。

  • navigation.navigate('Detail') 调用navigate方法传入路由name,可以跳转到对应页面。

  • navigation.push('Detail') 可以做到在Detail页面打开另一个Detail页面。

  • navigation.goBack() 路由回退。

  • 回退多个页面的方式:

    1. navigate('Home') 回退到Home页。

    2. navigation.popToTop() 会对到首页。

function DetailsScreen({ navigation }) {
  return (
    
      Details Screen
      

路由传参

步骤:

  1. navigation.navigate('RouteName', { /* params go here */ }) ,推荐使用JSON格式的参数。
  2. 在screen组件中读取传入的参数 route.params
function HomeScreen({ navigation }) {
  return (
    
      Home Screen
      

更新路由参数: navigation.setParams

传参回到上一个页面: navigation.navigate('',params)

传参到嵌套的路由器:

navigation.navigate('Account', {
  screen: 'Settings',
  params: { user: 'jane' },
});

配置标题栏

设置标题:(在 Screen 上设置 options

  1. options={{ title: 'My home' }}
  2. options={({ navigation, route }) => ({ title: route.params.name })}options 函数的参数包括 navigationroute ,我们只需用到 route
  3. 使用 navigation.setOptions({ title:'Updated!' }) 更新 options

修改header样式:(在 Screen 上设置 options

  1. headerStyle :参考View的样式写法
  2. headerTintColor
  3. headerTitleStyle :参考Text的样式写法
  4. 注意:iOS上,status bar的文字和图标是黑色的,详见适配方案

复用常规options:

  1. Stack.Navigator 上配置属性 screenOptions

自定义组件替换标题: headerTitle (默认是展示text的Text组件)

其他options:详见文档

function LogoTitle() {
  return (
    
  );
}

function StackScreen() {
  return (
    
        }}
      />
    
  );
}

header 按钮

添加header按钮

  1. headerRight
function StackScreen() {
  return (
    
       ,
          headerRight: () => (
            

header与它的screen组件交互

  1. 定义按钮时,在screen组件内使用 navigation.setOptions 而不是 options 属性,这样可以获取screen的props、state、context等。
function StackScreen() {
  return (
    
       ({
          headerTitle: props => ,
        })}
      />
    
  );
}

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        

配置回退按钮

  1. createStackNavigator 提供平台定制的默认回退按钮。
  2. headerBackTitle
  3. headerTruncatedBackTitle :文档
  4. headerBackImage :文档

覆盖回退按钮

  1. headerRight
  2. headerLeft
  3. 保留button只覆盖 onPress 方法,可以从 @react-navigation/stack 导出 HeaderBackButton 组件分配给 headerLeft 选项

嵌套导航器(todo)

在一个导航器的Screen内渲染另一个导航器。

  • Stack.Navigator
    • Home ( Tab. Navigator )
      • Feed ( Screen )
      • Messages ( Screen )
    • Profile ( Screen )
    • Settings ( Screen )
function Home() {
  return (
    
      
      
    
  );
}

function App() {
  return (
    
      
        
        
        
      
    
  );
}

注意:

  • 每个嵌套导航器保持自己的导航历史
  • 每个嵌套导航器有自己的 options
    • 根据子导航器的状态设置父Screen的options
  • 每个嵌套导航器有自己的参数
    • 如果需要从父Screen传参到子Screen,可以用React Context透传参数给children
  • 导航actions(比如 goBacknavigate )会被当前导航器处理,如果处理不了则会冒泡(被父导航器)处理
  • 导航器的特定方法在嵌套导航器中也可用:
    • 如果需要从父导航器dispatch actions到嵌套的子导航器,可以使用 navigation.dispatch
// 从父导航器dispatch actions到嵌套的子导航器
navigation.dispatch(DrawerActions.toggleDrawer());

{/** Drawer的openDrawer方法可以被Stack调用(navigation.openDrawer) */}

  
  {/** ... */}


{/** Drawer的openDrawer方法不可被Stack下的其他Screen调用 */}

  
  {/** ... */}


{/** Tab可以调用Stack的方法push和replace(navigation.push、navigation.replace) */}

  
  {/** ... */}

嵌套导航器不接收父导航器的事件,如需接受父导航器的事件,需要使用 navigation.dangerouslyGetParent() 显式监听

const unsubscribe = navigation
  .dangerouslyGetParent()
  .addListener('tabPress', (e) => {
    // Do something
  });

父导航器的UI渲染在子导航器的最顶端:

  • Drawer的每一个页面中嵌套Stack:抽屉出现在堆栈标题的上方
  • Stack中嵌套Drawer:抽屉显式在标题下方
  • Stack的首页中嵌套Tab:新页面会覆盖掉tab bar
  • Tab的每一个页面中嵌套Stack:tab bar一直显示,再按一下tab会回到stack的顶部

在嵌套导航器中跳转页面

  • Drawer.Navigator
    • Drawer.ScreenHome
    • Drawer.ScreenRoot
      • Stack.ScreenProfile
      • Stack.ScreenSettings
navigation.navigate('Root');
navigation.navigate('Root', { screen: 'Settings' });

在以前的版本中,所有配置都是静态的,但是现在通过动态配置,直到包含Screen的导航器呈现之前,react-navigation才知道哪些Screen可用以及在哪里可用。通常,Screen在导航进入之前不会渲染任何东西,因此尚未渲染的导航器配置不可用。这使得必须制定要导航到的层次结构。这也是为什么应该尽可能减少嵌套导航器以使代码更简单的原因。

将参数传递给嵌套导航器中Screen

// 指定params传递参数
navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    user: 'jane'
  },
});

// 深层嵌套
navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    screen: 'Sound',
    params: {
      screen: 'Media',
    },
  },
});

渲染定义在导航器中的初始路由

默认情况下,在嵌套导航器中导航Screen时,指定的screen会被用作初始screen,并且导航器上的initialRoute prop会被忽略。

如果需渲染特定的初始路由页面,可以设置 initial: false

navigation.navigate('Root', {
  screen: 'Settings',
  initial: false,
});

嵌套多个stack 导航器

当嵌套多个Stack导航器时,react-navigation将自动隐藏子stack导航器的标题,以避免重复的标题。但是根据场景不同,显示子stack导航器的标题,而不是隐藏父stack导航器的标题,可能更有用。可以设置 headerShown: false 解决该问题。完整示例 极少的情况,你需要同事显示父stack导航器和子stack导航器的标题,可以在子导航器上设置 headerShown: true

function Home() {
  return (
    
      
      
    
  );
}

function App() {
  return (
    
      
        
        
      
    
  );
}

嵌套的最佳做法:建议将嵌套导航器减少到最小。尝试通过尽可能少的嵌套来实现所需的行为。缺点如下

  • 会导致深度嵌套的视图层级结构,从而可能导致低端设备出现内存和性能问题
  • 嵌套相同类型的导航器可能会导致混淆的用户体验
  • 如果嵌套过多,导航到嵌套页面,配置深层链接等时将很难编写代码

导航生命周期

堆栈导航器中包含A和B两个screen。当导航到A页面时,A的 componentDidMount 被调用,当跳转到B时,B的 componentDidMount 被调用,但是A在stack中保持mounted,A的 componentWillUnmount 没有被调用。

当从B回退到A时,B的 componentWillUnmmount 被调用,但是A的 componentDidMount 没有被调用,因为A一直是mounted状态。

生命周期事件文档

  1. focus
  2. blur
function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // Screen was focused
      // Do something
    });

    return unsubscribe;
  }, [navigation]);

  return ;
}
  1. 代替手动添加事件监听,可以用 useFocusEffect hook去执行副作用,类似react的 useEffect hook。
  2. 使用 useIsFocused hook:返回boolean,表示屏幕是否聚焦
import { useFocusEffect } from '@react-navigation/native';

function Profile() {
  useFocusEffect(
    React.useCallback(() => {
      // Do something when the screen is focused

      return () => {
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, [])
  );

  return ;
}

打开全屏modal(需要嵌套导航器知识)

在Navigator上设置属性 mode="modal" ,调用 navigation.navigate 打开modal

function HomeScreen({ navigation }) {
  return (
    
      This is the home screen!
      

注意:

  • mode 可以设置为 card (默认)和 modal (iOS:从底部划出,从顶部向下滑动以关闭;安卓:无效)
  • 当调用 navigate 时,除了路由外,无需指定其他任何内容。(react-navigation尝试在最近的导航器上查找路由,然后在该位置执行操作)

Tab Navigator(todo)

文档

术语表

header: 屏幕顶端的矩形,包含回退按钮和标题

Navigator :包含子元素 Screen 。NavigationContainer是一个管理导航树和包含导航状态的组件。这个组件必须包着所有的导航结构。通常我们在app的顶部(通常在App.js里)渲染这个组件。

function App() {
  return (
    
       // <---- This is a Navigator
        
      
    
  );
}

Screen 组件:定义路由配置的组件

被配置在路由配置里的Screen组件(被 navigation.navigate 唤起的Screen)被提供了 navigation props,

const Stack = createStackNavigator();

const StackNavigator = (
  
    
    
  
);

Navigation prop navigation prop文档

  • dispatch
  • navigate
  • goBack
  • ...

Route proproute prop文档

  • params
  • key
  • name

Navigation state:结构如下,index指向当前的路由(B)

{
  key: 'StackRouterRoot',
  index: 1,
  routes: [
    { key: 'A', name: 'Home' },
    { key: 'B', name: 'Profile' },
  ]
}

Route

{
  key: 'B',
  name: 'Profile',
  params: { id: '123' }
}

兼容性层:降低升级的代码改动成本

  • 安装 @react-navigation/compat :
npm install @react-navigation/native @react-navigation/compat @react-navigation/stack
  • 使用 createCompatNavigatorFactory 包裹
-import { createStackNavigator } from 'react-navigation-stack';
+import { createStackNavigator } from '@react-navigation/stack';
+import { createCompatNavigatorFactory } from '@react-navigation/compat';
-const RootStack = createStackNavigator(
+const RootStack = createCompatNavigatorFactory(createStackNavigator)(
  {
    Home: { screen: HomeScreen },
    Profile: { screen: ProfileScreen },
  },
  {
    initialRouteName: 'Profile',
  }
);
  • NavigationActions 改为从 @react-navigation/compat 中导出
-import { NavigationActions } from 'react-navigation';
+import { NavigationActions } from '@react-navigation/compat';
  • @react-navigation/compat 导出的api包括:

    • Actions:
      • NavigationActions
      • StackActions
      • DrawerActions
      • SwitchActions
    • HOCs
      • withNavigation
      • withNavigationFocus
    • Navigators
      • createSwitchNavigator
    • Compatibility helpers
      • createCompatNavigatorFactory - 传入一个使用v5的api的导航器,返回一个使用v4 api的 createXNavigator
      • createCompatNavigationProp - 传入一个v5 的 navigation 对象 和route对象,返回一个v4的 navigation 对象
  • 兼容层处理了:

    • 使用v4的静态配置api,俄日不是基于api的组件
    • 改变了navigation对象上的方法名称去适配v4
    • 添加了对screenProps的支持(v5上被移除)
    • 导出了跟v4同名的action creators,例如NavigationActionsStackActionsSwitchActions
  • 兼容层没处理:v5动态api导致v4静态api不再具有某些功能

    • 不再包含navigator的props或者options。意味着你要传递给navigator的options可能因为重大改变而不同。
    • 通过在路由配置中定义path来定义旧式深层链接是不支持的
    • 导航到导航器的工作原理不同,我们无法导航到尚未渲染的导航器中的屏幕,并且参数不会合并到所有子屏幕。
    • 某些采取一系列操作的方法例如reset将不再支持。
    • 不导出createAppContainer,需要对容器使用v5的apiNavigationContainer
    • 如果使用的高阶api例如Redux集成、自定义路由器、actions,这些不再支持,并且你需要移除Redux集成。
  • 为什么用:

    • 允许使用新api写代码,同时使用旧api与代码集成
    • 建立在支持TypeScript的v5之上,旧代码也可以利用类型检查功能,对重构有帮助
    • 可以在旧组件中访问新api,例如navigation.setOptions或者一些新的hook例如useFocusEffect

在没用navigation props时navigate

  • 有时你需要从一个没有navigation prop的地方触发navigation action,比如redux middleware,在这种时候,你可以从navigation容器中dispatch navigation actions。

  • 如果你想要找一种方式,从一个不需要传navigation prop下去的组件里面navigate,可以用useNavigation。当你可以拿到navigation prop或者useNavigation时不要用这个方法,因为它的行为会有所不同,而且许多特定于屏幕的helper方法将不可用。

  • 你可以通过ref访问根导航器对象,将其传递给RootNavigation,我们稍后将使用该RootNavigation进行导航。

// App.js
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './RootNavigation';

export default function App() {
  return (
    {/* ... */}
  );
}

你可能感兴趣的:(tstat)