티스토리 뷰

반응형

안녕하세요, 오늘은 리액트네이티브에서 navigation, route를 사용할 때 유용한 제네릭타입을 만든 과정을 소개해보고자 합니다.

 

제네릭 타입을 사용하여 다양한 내비게이션 관련 속성과 타입을 정의하는 방법에 대해 알아보겠습니다. 

 

제네릭 타입은 TypeScript에서 매우 유용한 기능으로, 타입 안정성을 유지하면서 여러 유형의 값을 다룰 수 있습니다. 

이를 활용하여 내비게이션 관련 코드를 더 유연하고 재사용 가능하게 만들 수 있습니다. 

 

 

 


기존 사용법

기존에는 어떻게 사용하고 있었을까요? 

import React from 'react';

import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import {
  CompositeNavigationProp,
  RouteProp,
  useNavigation,
  useRoute,
} from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';

import { HomeStackParamList, TabParamList } from '@navigations/navigation.type';

export type HomeStackNavigationProp = CompositeNavigationProp<
  BottomTabNavigationProp<TabParamList, 'HomeTab'>,
  StackNavigationProp<HomeStackParamList>
>;

export type HomeScreenRouteProp = RouteProp<HomeStackParamList, 'HomeDetail'>;

const HomeScreen = () => {
  const navigation = useNavigation<HomeStackNavigationProp>();
  const route = useRoute<HomeScreenRouteProp>();
  const { id } = route.params;

  const navigateToHomeDetail = () => {
    navigation.navigate('HomeDetail');
  };

  return <></>;
};

export default HomeScreen;

이렇게 매 스크린마다 navigation, route에 필요한 props를 가져오고, 정의한 Stack param list도 가져와 타입을 지정해 주었습니다. 

타입추론은 되지만 n개의 스크린에 n번 이상의 props을 import 하는 것은 불필요하다는 생각이 들었습니다. 

 

 


제네릭타입 적용 결과 

제네릭 타입을 적용한 결과는 다음과 같습니다. 중복되었던 import 구문이 사라지고 navigation을 어떤 스크린으로 이동할지 더 명확해졌습니다.

import React from 'react';

import { useNavigation, useRoute } from '@react-navigation/native';
import { HomeTabScreenProps } from '@navigations/navigation.type';

const HomeScreen = () => {
  const navigation =
    useNavigation<HomeTabScreenProps<'EducationHome'>['navigation']>();
  const route = useRoute<HomeTabScreenProps<'EducationMaterial'>['route']>();
  const { id } = route.params;

  const navigateToHomeDetail = () => {
    navigation.navigate('HomeDetail');
  };

  return <></>;
};

export default HomeScreen;

 

 


과정

해당 코드를 리펙토링하면서 React Navigation 문서에서는 어떤 식으로 사용할지 궁금해서 찾아보았습니다.

 

천천히 읽어보던 중 문서의 마지막 즈음에서도 친절한 예제코드와 함께 제네릭타입을 만들어 관리하는 것을 권장하고 있었습니다. 

Organizing types
When writing types for React Navigation, there are a couple of things we recommend to keep things organized.
It's good to create a separate files (e.g. navigation/types.tsx) which contains the types related to React Navigation.Instead of using CompositeNavigationProp directly in your components, it's better to create a helper type that you can reuse.Specifying a global type for your root navigator would avoid manual annotations in many places.
export type HomeTabScreenProps<T extends keyof HomeTabParamList> =
  CompositeScreenProps<
    BottomTabScreenProps<HomeTabParamList, T>,
    RootStackScreenProps<keyof RootStackParamList>
  >;

문서의 예제인데요. 저는 여기서 더 나아가  BottomTabScreenProps까지 정의될 수 있도록 제네릭 타입을 생성하였습니다. 

 

 


1. 재사용할 타입 먼저 만들기 (StackScreenProps & BottomTabNavigationProps)

제네릭 안에 제네릭 타입을 생성할 계획이므로, 먼저 기본적으로 사용할 Props를 ParamList를 받는 제네릭을 생성했습니다. 

 

생성한 StackScreenProps는 ParamListBase를 확장한 제네릭 T를 받아들이는 타입으로, NavigationStackScreenProps를 T와 T의 키로 한정하여 스택 내비게이션 스크린의 속성을 나타냅니다.

//src/navigations/navigation.generic.type.ts
import type {
  StackScreenProps as NavigationStackScreenProps,
} from '@react-navigation/stack';

import type { ParamListBase } from '@react-navigation/native';

export type StackScreenProps<T extends ParamListBase> =
  NavigationStackScreenProps<T, keyof T>;

 

BottomTabNavigationProp은 제네릭 타입 T를 TabParamList의 키 중 하나 또는 undefined로 한정하는 타입입니다. T의 값에 따라 적절한 내비게이션 속성 타입을 반환합니다.

import type {
  BottomTabNavigationProp as NavigationBottomTabNavigationProp,
} from '@react-navigation/bottom-tabs';

export type BottomTabNavigationProp<
  T extends keyof TabParamList | undefined = undefined,
> = T extends keyof TabParamList
  ? NavigationBottomTabNavigationProp<TabParamList, T>
  : NavigationBottomTabNavigationProp<TabParamList>;

 


2. CompositeScreenProps 제네릭 타입 만들기 

CompositeScreenProps는 ParamListBase를 확장한 제네릭 T와 문자열 K를 받아들이는 타입입니다.

Navigation의 CompositeScreenProps를 NavigationBottomTabScreenProps <T, K>와 앞서 생성했던 제네릭타입인 StackScreenProps <T>로 한정하여 복합 내비게이션 스크린의 속성을 나타냅니다.

//src/navigations/navigation.generic.type.ts
import type {
  BottomTabNavigationProp as NavigationBottomTabNavigationProp,
  BottomTabScreenProps as NavigationBottomTabScreenProps,
} from '@react-navigation/bottom-tabs';

import type {
  CompositeScreenProps as NavigationCompositeScreenProps,
} from '@react-navigation/core';

export type CompositeScreenProps<
  T extends ParamListBase,
  K extends string,
> = NavigationCompositeScreenProps<
  NavigationBottomTabScreenProps<T, K>,
  StackScreenProps<T>
>;

 

 


3. 적용하기

생성한 타입을 Import해 각 Tab의 Stack parm list의 key를 넣어 적용시켜 줍니다. 

// src/navigations/navigation.type.ts


import {
  CompositeScreenProps,
} from './navigation.generic.type';

/** Composite Screen type
 * eg)
 * 1. with hooks
 *   const navigation = useNavigation<HomeTabScreenProps<'Home'>['navigation']>();
 *   const route = useRoute<HomeTabScreenProps<'Home'>['route']>();
 * 2. with props
 *  const HomeScreen = ({ navigation, route }: HomeTabScreenProps<'Home'>) => {
 *  // ..
 *  }
 */
 
export type HomeTabScreenProps<T extends keyof HomeStackParamList> =
  CompositeScreenProps<HomeStackParamList, T>;

export type MyPageTabScreenProps<T extends keyof MyPageStackParamList> =
  CompositeScreenProps<MyPageStackParamList, T>;

export type HomeStackParamList = ShareStackParamList & {
  Home: undefined;
  HomeDetail: {
  	id: number;
  }
};

export type MyPageStackParamList = ShareStackParamList & {
  MyPageHome: undefined;
  MyPageSetting: undefined;
};

 


4. 사용하기

Tab에 맞는 정의된 타입을 import 하여 사용합니다.

 

// src/components/elements/home/HomeScreen/HomeScreen.tsx

import React from 'react';

import { useNavigation, useRoute } from '@react-navigation/native';

import { HomeTabScreenProps } from '@navigations/navigation.type';

const HomeScreen = () => {
  const navigation =
    useNavigation<HomeTabScreenProps<'EducationHome'>['navigation']>();
  const route = useRoute<HomeTabScreenProps<'EducationMaterial'>['route']>();
  const { id } = route.params;

  const navigateToHomeDetail = () => {
    navigation.navigate('HomeDetail');
  };

  return <></>;
};

export default HomeScreen;

 

hook 이 아닌 props로 넘기는 경우에는 아래와 같이 사용할 수 있습니다. 

 

1. with hooks

const navigation = useNavigation<HomeTabScreenProps<'Home'>['navigation']>(); 
const route = useRoute<HomeTabScreenProps<'Home'>['route']>();

2. with props

const HomeScreen = ({ navigation, route }: HomeTabScreenProps <'Home'>) => {
  return(
   ...
  )
}

 


5. Bottom Tab Navigation Props 제네릭타입으로 사용하기

탭으로만 이동시키는 경우도 있을 수 있는데요. 앞서 생성했던 BottomTabNavagationProps타입을 쓰면 더 간단해집니다. 

앞서 만들었던 BottomTabNavagationProps타입은 아래와 같이 사용할 수 있습니다. 

 

기존

import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';

const ExampleScreen = () => {
    const navigation =
    useNavigation<BottomTabNavigationProp<TabParamList, 'HomeTab'>>();
    
   return(
   	...
   )
}

 

제네릭 적용 후 

 

1. 모든 TabParmList를 가져올 경우 

import { BottomTabNavigationProp } from '@navigations/navigation.generic.type';

const ExampleScreen = () => {
   const navigation = useNavigation<BottomTabNavigationProp>();
   
   return(
   ...
   )
}

 

2. 특정 탭(Tab)만 가져올 경우

import { BottomTabNavigationProp } from '@navigations/navigation.generic.type';=

const ExampleScreen = () => {
     const navigation = useNavigation<BottomTabNavigationProp<'HomeTab'>>();
   
   return(
   ...
   )
}

 


마치며

이번 글에서는 리액트 네이티브에서 내비게이션과 라우트를 다룰 때 유용한 제네릭 타입을 소개했습니다. 

제네릭 타입을 활용하면 타입 안정성을 유지하면서 내비게이션 관련 코드를 더 유연하고 재사용 가능하게 만들 수 있습니다. 

 

이번 리팩토링 과정을 통해 프로젝트의 유지보수성과 가독성을 높일 수 있어서 뿌듯합니다. 

 

개발 공부를 시작할 때 엘리님의 유투브 강의를 많이 보았는데 엘리님이 문서를 읽는 것에 대한 중요성을 강조하셨습니다. 

처음에는 낯선 용어들이 많아 이해하기 어려웠지만, 공부를 하면 할수록 문서를 읽는 것이 편안해지고 점점 먼저 찾게 되는 습관이 생겼습니다. 

 

문서를 꼼꼼히 읽고 예제 코드를 따라하는따라 하는 것이 많은 도움이 되었습니다. 문서 읽기가 어렵다면 계속해서 보고 따라 하는 연습을 꾸준히 해보시기를 추천합니다. 

 

추가로 navigation을 사용할 때 스택을 쌓을 수 있도록 타입을 추가로 만들어보려고 합니다. 여러분들도 제네릭 타입을 적극적으로 활용하여 타입 안정성과 코드의 가독성을 높이는 데 도전해 보시기 바랍니다.

 

 

 

Reference

반응형