tstat

作者: 发发呆哟 | 来源:发表于2020-09-15 15:57 被阅读0次

react-navigation笔记

安装依赖

npm install react-native-gesture-handler@1.7.0 react-native-reanimated@1.12.0 react-native-screens@2.10.1 react-native-safe-area-context@3.1.7 @react-native-community/masked-view@0.1.10 @react-navigation/compat@5.2.5 
@react-navigation/native@5.7.3 @react-navigation/stack@5.9.0

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

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 (
    <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
  );
}

安装 @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 (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}

function DetailsScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
    </View>
  );
}

const Stack = createStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ gestureEnabled: true }}>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Overview' }}/>
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

配置

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

component 属性接受组件,不接受一个render函数。不要传一个内联函数例如 component={() => <HomeScreen />} ,否则当父组件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 (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

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 <Button theme={this.context} />;
  }
}

  • 用render callback而不是定义一个component prop。默认情况下react-navigation底层做了优化防止重复渲染,但是使用render callback则会移除这些优化。如果用render callback,需要确保Screen组件使用了 React.memoReact.PureComponent
<Stack.Screen name="Home">
  {props => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>

路由跳转

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 (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

// ... other code from the previous section
  • 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 (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Button
        title="Go to Details... again" onPress={() => navigation.push('Details')} />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
      <Button title="Go back to first screen in stack" onPress={() => navigation.popToTop()}
      />
    </View>
  );
}

路由传参

步骤:

  1. navigation.navigate('RouteName', { /* params go here */ }) ,推荐使用JSON格式的参数。
  2. 在screen组件中读取传入的参数 route.params
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => {
          /* 1. Navigate to the Details route with params */
          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });
        }}
      />
    </View>
  );
}

function DetailsScreen({ route, navigation }) {
  /* 2. Get the param */
  const { itemId } = route.params;
  const { otherParam } = route.params;
  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={() => navigation.push('Details', { itemId: Math.floor(Math.random() * 100) }}
      />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

更新路由参数: 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 (
    <Image
      style={{ width: 50, height: 50 }}
      source={require('@expo/snack-static/react-native-logo.png')}
    />
  );
}

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerTitle: props => <LogoTitle {...props} /> }}
      />
    </Stack.Navigator>
  );
}

header 按钮

添加header按钮

  1. headerRight
function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          // 在options里 this 不指向HomeScreen实例,不能调用setState或其他实例方法
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        }}
      />
    </Stack.Navigator>
  );
}

header与它的screen组件交互

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

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

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount(c => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

配置回退按钮

  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 (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Messages" component={Messages} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="Profile" component={Profile} />
        <Stack.Screen name="Settings" component={Settings} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

注意:

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

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

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

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

嵌套导航器不接收父导航器的事件,如需接受父导航器的事件,需要使用 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 (
    <NestedStack.Navigator>
      <NestedStack.Screen name="Profile" component={Profile} />
      <NestedStack.Screen name="Settings" component={Settings} />
    </NestedStack.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <RootStack.Navigator>
        <RootStack.Screen
          name="Home"
          component={Home}
          options={{ headerShown: false }}
        />
        <RootStack.Screen name="EditPost" component={EditPost} />
      </RootStack.Navigator>
    </NavigationContainer>
  );
}

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

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

导航生命周期

堆栈导航器中包含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 <ProfileContent />;
}
  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 <ProfileContent />;
}

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

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

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is the home screen!</Text>
      <Button
        onPress={() => navigation.navigate('MyModal')}
        title="Open Modal"
      />
    </View>
  );
}

function DetailsScreen() {
  return (
    <View>
      <Text>Details</Text>
    </View>
  );
}

function ModalScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is a modal!</Text>
      <Button onPress={() => navigation.goBack()} title="Dismiss" />
    </View>
  );
}

const MainStack = createStackNavigator();
const RootStack = createStackNavigator();

function MainStackScreen() {
  return (
    <MainStack.Navigator>
      <MainStack.Screen name="Home" component={HomeScreen} />
      <MainStack.Screen name="Details" component={DetailsScreen} />
    </MainStack.Navigator>
  );
}

function RootStackScreen() {
  return (
    <RootStack.Navigator mode="modal">
      <RootStack.Screen
        name="Main"
        component={MainStackScreen}
        options={{ headerShown: false }}
      />
      <RootStack.Screen name="MyModal" component={ModalScreen} />
    </RootStack.Navigator>
  );
}

注意:

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

Tab Navigator(todo)

文档

术语表

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

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

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator> // <---- This is a Navigator
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

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

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

const Stack = createStackNavigator();

const StackNavigator = (
  <Stack.Navigator>
    <Stack.Screen
      name="Home"
      component={HomeScreen} // <----
    />
    <Stack.Screen
      name="Details"
      component={DetailsScreen} // <----
    />
  </Stack.Navigator>
);

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 (
    <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
  );
}

相关文章

  • tstat

    react-navigation笔记 安装依赖 在根文件顶部引入,否则应用会崩溃 将整个应用包装在 Navigat...

网友评论

      本文标题:tstat

      本文链接:https://www.haomeiwen.com/subject/veefyktx.html