TypeScript在React Native中的实战

上一篇文章中我们已经配置好TypeScript和文件绝对路径的查找. 接下来我们将实战使用Typescript.
我们稍微扩展一下项目, 首先在src/pages/下新建Home.tsx, Detail.tsx, Found.tsx, Listen.tsx, Account.tsx, 然后在src/navigator下新建index.tsx.

接着我们安装React Native导航器来管理路由导航. 具体安装参考官方文档react-navigation官方文档.安装配置完成后我们继续完善应用, 使页面可以跳转传参.

假设我们的应用有以下两个需求

  • 四个Tab的BottomTab, 点击不同的tab渲染对应的标题.
  • 点击Home的中按钮跳转到Detail页面并传参.

我们先配置路由, 修改navigator/index.tsx:

import {NavigationContainer} from '@react-navigation/native'
import {createStackNavigator} from '@react-navigation/stack

const Stack = createStackNavigator()

class Navigator extends React.Component {
  render () {
    return (
      <NavigationContainer>
        <Stack.navigator>
          <Stack.Screen name="BottomTabs" component={BottomTabs} />
          <Stack.Screen name="Detail" component={Detail} />
        </Stack.Navigator>
      </NavigationContainer>
    )
  }
}

export default Navigator

@react-navigation/stack堆栈导航器. 我们让堆栈导航器包裹标签导航器(BottomTabs), 以便在BottomTabs中直接渲染Home组件. 同样的点击不同的Tab时渲染对应的标题.

同样的我们也可以让标签导航器包裹堆栈导航器, 不过这中方法会导致我们点击不同的Tab时渲染的都是同样的标题.

import {StackNavigationProp} from '@react-navigation/stack
......
type RootStackParamsList = {
  BottomTabs: {
    screen?: String
  },
  Detail: {
    id: String
  }
}

export type RootStackNavigation = StackNavigationProp<RootStackParamsList>
const Stack = createStackNavigator<RootStackParamsList>()

class Navigator extends React.Component {
  ...
}

我们声明了一个RootStackParamsList, 它包含我们要渲染的BottomTabsDetail组件, 这两个组件都需要接受一个String类型的参数

同时我们实例化createStackNavigation, 实例化后的StackRootStackParamsList类型, 这样我们才能推断出渲染什么组件.

另外的我们导出了一个RootStackNavigation, 它同样是RootStackParamsList类型, 具体可以查看官方文档type-checking-screens

接下来我们完善BottomTabs:

import {RouteProp, TabNavigationState} from '@react-navigation/core'
import {getFocusedRouteNameFromRoute} from '@react-navigation/native'
import {createBottomNavigator} from '@react-navigation/bottom-tabs'
import {RootStackNavigation, RootStackParamsList} from './index'

export type BottomTabParamsList = {
  Home: undefined,
  Listen: undefined,
  Found: undefined,
  Account: undefined
}

type Route = RouteProp<RootStackParamsList, 'BottomTabs'> & {
  state?: TabNavigationState
}

export interface IProps {
  navigation: RootStackNavigation,
  route: Route
}

const Tabs = createBottomNavigator<BottomTabsParamsList>()

fucntion getHeaderTitle(route: Route) {
  const routeName = getFocusedRouteNameFromRoute(route)
  switch(routeName) {
    case 'Home':
      return '首页'
    case 'Listen':
      return '我听'
    case 'Found':
      return '发现'
    case 'Account':
      return '我的'
    default:
      return '首页'
  }
} 

class BottomTabs extends React.Component<IProps> {
  componentDidUpdate () {
    const {navigation, route} = this.props
    navigation.setOptions({
      headeTitle: getHeaderTitile(route)
    })
  }
  render () {
    return (
      <Tabs.Navigator>
        <Tabs.screen
          name="Home"
          component={Home}
        />
        <Tabs.screen
          name="Found"
          component={Found}
          options={{
            tabBarLabel: '发现'
          }}
        />
        <Tabs.screen
          name="Listen"
          component={Listen}
          options={{
            tabBarLabel: '我听'
          }}
        />
        <Tabs.screen
          name="Account"
          component={Account}
          options={{
            tabBarLabel: '我的'
          }}
        />
      </Tabs.Navigator>
    )
  }
}

当我们点击Tab时会触发componentDidUpdate这个生命周期, 在此生命周期内可以为当前Tab设置页面标题.

我们定义了一个方法getHeadertitle, 在该方法内用getFocusedRouteNameFromRoute获取当前激活的路由的name. getHeaderTitle接收一个Route类型, 该类型是一个交叉类型, 它既包含RouteProp也就是路由本身特性又包含TabNavigationState的特性.

到此BottomTabs就完成了. 接下来我们完成页面跳转并传参, 修改Home.tsx:

import {RootStackNavigation} from '../navigator/index'

interface IProps {
  navigation: RootStackNavigation
}

class Home extends React.Component<IProps> {
  onPress = () => {
    const {navigation} = this.props
    navigation.navigate('Detail', {
      id: 'sfasf'
    })
  }
  render () {
    return (
      <View>
        <Button title='跳转到Detail' onPress={this.onPress}></Button>
      </View>
    )
  }
}

修改Detail.tsx

import {RouteProp} from '@react-navigation/core'
import {RootStackParamsList} from '../navigator/index'

interface IProps {
  route: RouteProp<RootStackParamsList, 'Detail'>
}

class Detail extends React.Component<IProps> {
  render () {
    const {route} = this.props
    return (
      <View>
        <Text>{route.params?.id}</Text>
      </View>
    )
  }
}

Detail通过route来获取传递过来的参数id, 所以route应该是RouteProp类型. 最后通过route.params.id来获取参数.