Tab Navigator에서 params에 따라 렌더링하기

화영화영
3 min read

React Navigation은 react native에서 화면이동(routing) 시 사용되는 라이브러리로, 페이지가 쌓이는 구조(stack 구조)를 제공한다. Stack 뿐만 아니라 Tab(하단탭), Drawer(옆에서 열리는 전체메뉴) 등의 화면 이동 방식(네비게이터)도 가지고 있다. 네비게이터들은 서로 중첩이 가능하다. 우리 프로젝트에서는 Stack과 Tab 네비게이터를 사용했고, Stack 내 하나의 화면에 Tab을 중첩하여 사용했다.

📘 기본사용방법

  1. Stack 네비게이터를 생성 - 리액트 네비게이션은 <NavigationContainer> 내에서 실행할 수 있다.
    Stack.Screen은 화면의 이름과 렌더링되는 컴포넌트를 지정한다.
  • 네비게이터 타입의 Stack.Screen의 name이 될 수 있다. 키의 타입undefined면 화면 이동 시 params가 필요없는 화면이고, 객체로 들어간다면 해당 객체를 화면 이동시 params로 넘겨준다.
    navigation.navigate('Detail', {idx: item.index}
export type RootStackParamList = {
  Tab: NavigatorScreenParams<HomeTabParamList>;
  Shop: undefined;
  Detail: { idx: number };
}

const Stack = createNativeStackNavigator<RootStackParamList>(); // 스택 네비게이터 생성

const App = () => (
  <NavigationContainer>  
    <Stack.Navigator>
      <Stack.Screen name='Tab' component={TabNavigator} />
      <Stack.Screen name='Shop' component={Shop} />
      <Stack.Screen name='Detail' component={Detail} />
    </Stack.Navigator>
  </NavigationContainer>
)
  1. 탭 네비게이터를 생성 - 탭 네비게이터는 스택의 화면 중 하나로 들어가고 있다. 하단 탭의 컴포넌트를 tabBar로 설정할 수 있고 props는 BottomTabBarProps 타입이 전달되어 해당 탭의 이름과 파람스를 알 수 있다.
type HomeTabParamList = {
  Home: {tab: 'first' | 'second'} | undefined;
  Mypage: undefined;
  Menu: undefined;
}

// BottomTabBarProps의 속성을 사용해 탭 클릭 시 화면 이동
const TabBar = ({state, navigation}) => (
  // ... 
  const onPress = () => {
    navigation.navigate(state.route.name, state.route.params);
  }
 // ...
)

const Tab = createBottomTabNavigator<HomeTabParamList>();  // 탭 네비게이터 생성

const TabNavigator = () => (
  <Tab.Navigator tabBar={TabBar}>
    <Tab.Screen name='Home' component={Home} />
    <Tab.Screen name='Mypage' component={Mypage} />
    <Tab.Screen name='Menu' component={Menu} />
  </Tab.Navigator>
)
  • 탭 네비게이터 간의 이동은 화면이 전환되기만 할 뿐 렌더링되지 않는다. 반면에 스택 네비게이터는 화면 이동 시 새로 렌더링된다. 스택 네비게이터는 화면이 쌓이는 구조이기때문에 이전화면으로 이동이 가능하며, 새로운 화면으로 이동할 때는 새로운 스크린이 push 되는 방식이다.

⚠️ 문제

  • 그래서 탭 간의 이동을 할때, 특히나 파람스에 따라 다르게 렌더링되는 화면의 이동이 잘 되지 않았다. 기존 스택 네비게이션에서 사용하는 방식을 사용하면 해당화면으로 이동은 하지만, 파람스에 따라 tab을 보여주진 않았다.
  • 그리고 특이하게 route.params?.tab가 변경되면 탭의 상태(select)를 변경하도록 했는데 setSelect가 두번 동작하면서 기존 tab으로 돌아가는 현상이 일어났다. first → second → first 이런식으로..
// Menu 화면에서 Home 화면으로 이동
// type CombinedParamList = RootStackParamList & HomeTabParamList;
navigation.navigate('Home', { tab: 'second' } as CombinedParamList['Home']);

// 위 네비게이션이 동작하면 Home 으로 이동하면서 params 변경에 따라 select가 변경되어 렌더링 되는 것을 예상했다
// Home.tsx
  const [select, setSelect] = useState<THomeTab>(route.params?.tab ?? 'first');

  useEffect(() => {
    console.log('2️⃣ 셀렉트 변경됨', select);
  }, [select]);

  useEffect(() => {
    if (route?.params?.tab) {
      setSelect(route?.params?.tab);
      console.log('1️⃣ 탭 변경됨', route?.params?.tab);
    }
  }, [route.params?.tab]);

위의 코드는 아래처럼 찍힌다

LOG 1️⃣ 탭 변경됨 second     # route.params?.tab이 second로 변경
LOG 2️⃣ 셀렉트 변경됨 second  # setSelect가 작동하여 select가 second로 변경
LOG 2️⃣ 셀렉트 변경됨 first   # ????

💡 navigation.dispatch(CommonActions.reset(...))

탭 네비게이션 이동시 리렌더링이 되지 않는것은 알겠으나 왜 setSelect가 두번 실행되어 기존 상태로 돌아가는지는 알아내지 못했다. 대신 홈의 각각의 탭으로 이동할 때는 네비게이션 기록을 초기화하여 새롭게 렌더링하는 navigation.dispatch(CommonActions.reset(...)) 이 방식을 사용했다. 기존의 화면 상태나 파라미터가 남지 않고 완전 초기화되어 렌더링하는 방식이다.

navigation.dispatch(
  CommonActions.reset({
  routes: [{ name: item.page, params: item.params as CombinedParamList['Home'] }],
  }),
);

react-navigation 라이브러리 사용 방법을 정리하면서 네비게이션이 어떤 구조로 이루어져있고, 동작하는지, 그리고 각각의 네비게이터의 특성에 따라 다르게 실행하는 방법도 익힐 수 있었다. 탭 네비게이터에서 새로 렌더링 하지않고 파람스에 따라 화면을 렌더링 시키는 다른 방법이 있는지도 궁금하다. 작업 중 일어난 버그를 완전히 해결하진 못했지만 사용하다보면 또 새롭게 알아가겠지 싶다.

0
Subscribe to my newsletter

Read articles from 화영 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

화영
화영