在 TypeScript 中進行類型檢查
React Navigation 使用 TypeScript 編寫,並為 TypeScript 專案匯出類型定義。
類型檢查導航器
若要類型檢查我們的路由名稱和參數,我們需要做的第一件事,就是為「路由名稱」至「路由參數」建立一個物件類型。例如,假設我們的根導航器中有一個名為 個人資料
的路由,其應該有一個參數 userID
type RootStackParamList = {
Profile: { userId: string };
};
類似地,我們需要對每個路由執行相同的動作
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
指定 undefined
表示路由沒有參數。具有 undefined
的聯合類型(例如 SomeType | undefined
)表示參數是選擇性的。
在定義好對應關係之後,我們需要告訴導航器使用它。為此,我們可以將其作為泛型傳遞給 createXNavigator
函數
import { createStackNavigator } from '@react-navigation/stack';
const RootStack = createStackNavigator<RootStackParamList>();
然後我們可以使用它
<RootStack.Navigator initialRouteName="Home">
<RootStack.Screen name="Home" component={Home} />
<RootStack.Screen
name="Profile"
component={Profile}
initialParams={{ userId: user.id }}
/>
<RootStack.Screen name="Feed" component={Feed} />
</RootStack.Navigator>
這將提供 Navigator 和 Screen 組件屬性的類型檢查和 IntelliSense。
包含對應關係的類型必須為類型別名 (例如:type RootStackParamList = { ... }
)。不能是介面 (例如:interface RootStackParamList { ... }
)。也不應延伸 ParamListBase
(例如:interface RootStackParamList extends ParamListBase { ... }
)。這麼做會導致不正確的類型檢查,允許傳入不正確的路由名稱。
輸入檢查螢幕
若要輸入檢查我們的螢幕,我們需要註解螢幕接收到的 navigation
屬性和 route
屬性。React Navigation 中的瀏覽器套件匯出一般類型為 corresponding 瀏覽器中的 navigation
和 route
屬性定義類型。
舉例來說,你可以使用 Native Stack Navigator 的 NativeStackScreenProps
。
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
type Props = NativeStackScreenProps<RootStackParamList, 'Profile'>;
該類型有 3 個泛型
- 我們先前定義的參數清單物件
- 螢幕所屬的路由名稱
- 瀏覽器的 ID (選用)
如果你有針對瀏覽器的 id
屬性,你可以這麼做
type Props = NativeStackScreenProps<RootStackParamList, 'Profile', 'MyStack'>;
這允許我們輸入檢查路由名稱和參數,你透過 navigate
、push
等方式瀏覽。當前路由的名稱對於輸入檢查 route.params
中的參數以及呼叫 setParams
時是必要的。
類似地,你可以匯入 @react-navigation/stack
的 StackScreenProps
、@react-navigation/drawer
的 DrawerScreenProps
、@react-navigation/bottom-tabs
的 BottomTabScreenProps
等。
然後你可以使用上面定義的 Props
類型註解你的組件。
針對函式組件
function ProfileScreen({ route, navigation }: Props) {
// ...
}
針對類別組件
class ProfileScreen extends React.Component<Props> {
render() {
// ...
}
}
你可以從 Props
類型取得 navigation
和 route
的類型,如下所示
type ProfileScreenNavigationProp = Props['navigation'];
type ProfileScreenRouteProp = Props['route'];
或者,你也可以分別註解 navigation
和 route
屬性。
若要取得 navigation
屬性的類型,我們需要從瀏覽器匯入對應的類型。例如,針對 @react-navigation/native-stack
的 NativeStackNavigationProp
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type ProfileScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
'Profile'
>;
類似地,你可以從 @react-navigation/stack
匯入 StackNavigationProp
、從 @react-navigation/drawer
匯入 DrawerNavigationProp
、從 @react-navigation/bottom-tabs
匯入 BottomTabNavigationProp
等
如果要取得 route
prop 的類型,我們需要從 @react-navigation/native
使用 RouteProp
類型
import type { RouteProp } from '@react-navigation/native';
type ProfileScreenRouteProp = RouteProp<RootStackParamList, 'Profile'>;
建議你建立一個區別的 types.tsx
檔案,供你保留類型並在元件檔案中匯入,而非在每個檔案中重複。
巢狀導航
在巢狀導航工具列中檢查類型畫面和參數
你可以導覽至 Nest 導航工具列中的一個畫面,方法是提供巢狀畫面的 screen
和 params
屬性
navigation.navigate('Home', {
screen: 'Feed',
params: { sort: 'latest' },
});
為了能夠檢查類型,我們需要從包含巢狀導航工具列的畫面中擷取出參數。可以使用 NavigatorScreenParams
工具執行這項操作
import { NavigatorScreenParams } from '@react-navigation/native';
type TabParamList = {
Home: NavigatorScreenParams<StackParamList>;
Profile: { userId: string };
};
結合導航 prop
當你巢狀導航工具列時,畫面的導航 prop 是多個導航 prop 的組合。例如,如果我們在堆疊內有一個分頁,navigation
prop 同時具有 jumpTo
(來自於切換導航工具列)和 push
(來自堆疊導航工具列)。若要輕鬆地結合來自多個導航工具列的類型,你可以使用 CompositeScreenProps
類型
import type { CompositeScreenProps } from '@react-navigation/native';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { StackScreenProps } from '@react-navigation/stack';
type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
StackScreenProps<StackParamList>
>;
CompositeScreenProps
類型取用 2 個參數,第一個參數是主導航工具列的 prop 類型(我們案例中,擁有此畫面的導航工具列的類型,也就是包含 Profile
畫面的切換導航工具列),第二個參數是次要的導航工具列的 prop 類型(父導航工具列的類型)。主要類型應始終將畫面的路線名稱作為其第二個參數。
對於多個父導航工具列,這個次要類型應予巢狀
type ProfileScreenProps = CompositeScreenProps<
BottomTabScreenProps<TabParamList, 'Profile'>,
CompositeScreenProps<
StackScreenProps<StackParamList>,
DrawerScreenProps<DrawerParamList>
>
>;
如果個別註解 navigation
prop,你可以改用 CompositeNavigationProp
。用法類似於 CompositeScreenProps
import type { CompositeNavigationProp } from '@react-navigation/native';
import type { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import type { StackNavigationProp } from '@react-navigation/stack';
type ProfileScreenNavigationProp = CompositeNavigationProp<
BottomTabNavigationProp<TabParamList, 'Profile'>,
StackNavigationProp<StackParamList>
>;
註解 useNavigation
註解 useNavigation
在類型上不安全,因為類型參數無法經過靜態驗證。建議改指定預設類型。
要註解我們從 useNavigation
取得的 navigation
屬性,我們可以使用類型參數
const navigation = useNavigation<ProfileScreenNavigationProp>();
註解 useRoute
註解 useRoute
並不是類型安全的,因為類型參數無法靜態驗證。當可以時,請優先使用 route
屬性。對於不需要特定路徑類型的通用程式碼,請使用 useRoute
。
要註解我們從 useRoute
取得的 route
屬性,我們可以使用類型參數
const route = useRoute<ProfileScreenRouteProp>();
註解 options
和 screenOptions
當你將 options
傳遞至 Screen
或 screenOptions
屬性傳遞至 Navigator
元件時,它們已經經過類型檢查,你無需進行特殊操作。不過,有時你可能想將選項萃取出成一個獨立的物件,且你可能想對它進行註解。
要註解選項,我們需要從導覽器匯入對應的類型。例如,對於 @react-navigation/stack
的 StackNavigationOptions
import type { StackNavigationOptions } from '@react-navigation/stack';
const options: StackNavigationOptions = {
headerShown: false,
};
類似地,你可以從 @react-navigation/drawer
匯入 DrawerNavigationOptions
,從 @react-navigation/bottom-tabs
匯入 BottomTabNavigationOptions
等。
當使用 options
和 screenOptions
的函式形式時,你可以使用與用於註解 navigation
和 route
屬性相同的類型註解引數。
註解 NavigationContainer
上的 ref
如果你使用 createNavigationContainerRef()
方法來建立 ref,你可以註解它以類型檢查導航動作
import { createNavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef = createNavigationContainerRef<RootStackParamList>();
對 useNavigationContainerRef()
也是如此
import { useNavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef = useNavigationContainerRef<RootStackParamList>();
如果你使用一般 ref
物件,你可以將泛型傳遞至 NavigationContainerRef
類型。
使用 React.useRef
鉤子時的範例
import type { NavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef =
React.useRef<NavigationContainerRef<RootStackParamList>>(null);
使用 React.createRef
時的範例
import type { NavigationContainerRef } from '@react-navigation/native';
// ...
const navigationRef =
React.createRef<NavigationContainerRef<RootStackParamList>>();
指定 useNavigation
、Link
、ref
等的預設類型
你可以指定根導覽器的全域類型,取代手動註解這些 API,此類型將作為預設類型。
要這麼做,你可以將這個程式碼片段加入你的程式碼庫中
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
RootParamList
介面可讓 React Navigation 知道你的根導覽器接受的參數。我們在此擴充類型 RootStackParamList
,因為這是放置在根部的堆疊導覽器的參數類型。此類型的名稱並不重要。
如果您在應用程式中大量使用 useNavigation
、 Link
等,指定此類型很重要,因為它將確保類型安全。它還將確保您在 linking
道具上具有正確的巢狀結構。
組織類型
在撰寫 React 導航的類型時,我們建議採取一些措施來整理。
- 最好建立一個獨立的檔案 (例如
navigation/types.tsx
),其中包含與 React 導航相關的類型。 - 不要在元件中直接使用
CompositeNavigationProp
,最好建立一個可以重複使用的輔助類型。 - 為根導航器指定全域類型可避免在許多地方進行手動註解。
考量這些建議,包含類型的檔案可能如下所示
import type {
CompositeScreenProps,
NavigatorScreenParams,
} from '@react-navigation/native';
import type { StackScreenProps } from '@react-navigation/stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
export type RootStackParamList = {
Home: NavigatorScreenParams<HomeTabParamList>;
PostDetails: { id: string };
NotFound: undefined;
};
export type RootStackScreenProps<T extends keyof RootStackParamList> =
StackScreenProps<RootStackParamList, T>;
export type HomeTabParamList = {
Popular: undefined;
Latest: undefined;
};
export type HomeTabScreenProps<T extends keyof HomeTabParamList> =
CompositeScreenProps<
BottomTabScreenProps<HomeTabParamList, T>,
RootStackScreenProps<keyof RootStackParamList>
>;
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
現在,在註解元件時,您可以撰寫
import type { HomeTabScreenProps } from './navigation/types';
function PopularScreen({ navigation, route }: HomeTabScreenProps<'Popular'>) {
// ...
}
如果使用 useRoute
等鉤子,可以撰寫
import type { HomeTabScreenProps } from './navigation/types';
function PopularScreen() {
const route = useRoute<HomeTabScreenProps<'Popular'>['route']>();
// ...
}