LIKET - RN에서 웹뷰를 앱스럽게 개선하기

민경찬·2024년 8월 18일
69

프론트엔드

목록 보기
1/1
post-thumbnail

라이켓은 다양한 문화생활 정보를 공유하고 나만의 문화생활 기록을 남길 수 있는 서비스를 제공하고 있습니다.
태그별, 지역별로 관심있는 정보들만 골라보고 쉽게 문화생활을 즐겨보세요.

안녕하세요. LIKET팀의 백엔드 개발자 민경찬입니다. 오늘은React Native를 사용하여 웹뷰를 네이티브스럽게 만들었던 과정을 얘기해보려고 합니다.

잠깐, 왜 백엔드인데 React Native를 썼냐구요?
프론트 인력이 부족하여 Next.js와 React Native까지 함께 개발하게 되었답니다.😀


⭐️ RN으로 웹뷰 띄우기

React Native에서 단일 웹뷰를 띄우는 것은 너무나도 간단한 일입니다.

<WebView source={{uri: WEB_VIEW_URL + path}}/>

웹뷰 컴포넌트를 지원해주기 때문에 큰 어려움없이 웹 화면을 앱에서 띄울 수 있습니다.

그러나 웹뷰만을 이용하면 너무나도 앱스럽지 않습니다. 왜그런걸까요?

😧 부자연스러운 헤더

웹뷰에는 바운스라는 효과가 존재합니다. 스와이프를 할 때 가장자리에 닿으면 통통 튀기는 효과입니다.

그러나, 바운스효과는 웹뷰 전체에 발생하는 효과이기 때문에 헤더까지 튕기는 문제가 있습니다.

😧 같이 렌더링되는 Bottom-Tab

대표적으로 메인 페이지지도 페이지는 바텀 탭을 가지고 있습니다.

Next.js에서 이를 메인 페이지지도 페이지로 분리를 하여 관리하였고 그 결과...

웹뷰가 전환될 때 바텀탭이 한 번 깜박이는 현상이 발생하였습니다.

😧 뒤로가기 액션의 부재

단일 웹뷰에서는 화면을 왼쪽에서 오른쪽으로 스와이프하여 뒤로가기를 할 수 없습니다.

앱에서는 스택의 개념이 있지만 단일 웹뷰로는 스택을 구현할 수 없기 때문이죠.

만약 위 액션을 사용할 수 없다면, 페이지 내부에 뒤로가기 버튼을 클릭하여 뒤로가야합니다.
너무나도 불편하죠.

하나씩 해결해봅시다.

1. 헤더 고정하기

헤더를 고정하기 위해서 2가지 방법을 시도해봤습니다.

  1. Bounce Effect 끄기
  2. 헤더를 Native로 따로 개발하기

Bounce를 끄는 것은 구현 난이도가 가장 쉬웠습니다.

<WebView bounces={false} />

그러나 앱의 매력을 많이 떨어뜨린다고 생각했고 결국 헤더는 Native로 구현하기로 하였습니다.

Next.js에서는 웹뷰 요청인지를 분기처리하여 헤더를 지워주는 로직을 넣었습니다.
그 후, 헤더 컴포넌트만 Native로 구현하여 깔끔하게 헤더를 고정하였습니다.

2. Stack 구현하기

Next.js에서 웹뷰임을 감지하여 페이지 이동 액션을 다르게 처리해주었습니다.

아래는 Next.js에서 사용하는 커스텀 라우팅 함수입니다.

/**
 * Push 라우팅 메서드.
 * 웹뷰: 스크린을 이동시키며 해당 스크린의 웹뷰로 입력한 path를 띄움
 * 웹: 일반적인 라우팅
 *
 * @param router useRouter 훅 인스턴스
 * @param path 스크린에서 띄울 웹뷰의 화면 경로
 * @param option 스크린 이동에 대한 옵션
 */
export const stackRouterPush = (
  router: NextRouter | AppRouterInstance,
  option: StackRouterPushOption
) => {
  if (isApp()) {
    window.ReactNativeWebView.postMessage(
      JSON.stringify({
        type: WebViewEventType.ROUTER_EVENT,
        path: path, 
        screen: option?.screen, // 앱 환경의 경우 RN의 스크린을 변경 시켜줌
        isStack: option?.isStack,
      })
    );
  } else {
    router.push(path); // 앱 환경이 아니면 단순한 라우팅
  }
};

더 이상 router.push 메서드는 사용하지 않습니다.
대신 stackRouterPush를 이용합니다.

RN에서는 이렇게 처리하고 있습니다.

export const routerOnMessage =
  (navigation) => (e: WebViewMessageEvent) => {
    const nativeEvent = JSON.parse(e.nativeEvent.data);

    const type = nativeEvent?.type;

    if (type === WebViewEventType.ROUTER_EVENT) { // 라우팅 이벤트가 들어오면
      const option: StackRouterPushOption = nativeEvent;

      // 스택을 쌓도록 구현
      const pushAction = StackActions.push(option.screen, {
        path: option.path,
      });
      navigation.dispatch(pushAction);
    }
  };

React Native에서 모든 WebviewonMessage이벤트를 달아주어 기존 코드를 최대한 바꾸지 않도록 하였습니다.

당연히 아래 코드처럼, 미리 스크린이 준비되어있어야겠죠!

<NavigationContainer>
  <Stack.Navigator>
    <Stack.Screen 
      name={ScreenTYPE.MAIN}
      component={HomeScreen}
      options={{headerShown: false, animation: 'none'}}
      />
    <Stack.Screen
      name={ScreenTYPE.LOGIN}
      component={LoginScreen}
      options={{headerShown: false}}
    />
// ....

짜잔, 깔끔하게 앱스러워진 것을 볼 수 있습니다.

3. 바텀 탭 고정하기

메인 페이지마이 페이지는 별도의 페이지입니다.
따라서 페이지가 전환될 때, 바텀 탭 또한 다시 렌더링됩니다.

이를 해결하고자 Next.js에서 바텀 탭을 사용하는 페이지를 전부 하나의 페이지로 합쳐버렸습니다.

그러나 이는 서버 사이드 렌더링의 효과를 떨어뜨리고, 페이지 로딩 시간이 더 오래 걸리는 결과를 초래하였습니다.

고민을 꽤 많이 했던 파트인데, 이를 페이지를 합치지 않고 스크린을 합침으로써 해결하였습니다.

{/* path = "/", "/map", "/mypage" */}
<WebView
  ref={webViewRef}
  source={{uri: WEB_VIEW_URL + path}}   
/>

{/* RN에서 구현한 바텀탭 */}
<BottomTab />   

또한 BottomTab을 React Native에서 새롭게 구현하였습니다.
화면이 전환될 때 BottomTab은 전환되지 않게하기 위함입니다.

🤭 어떻게 바뀌었나요?

확실히 더 앱스러워진 모습을 볼 수 있습니다.


결과

모바일 웹을 더욱 앱스럽게 구현하기 위해서 시도했던 다양한 방법들을 공유해보고 싶었습니다.

자료가 많이 없더라구요... 정말 많은 시도를 해봤고 정말 많은 시행착오를 겪었던 것 같아요.

읽어주셔서 감사합니다.

2개의 댓글

comment-user-thumbnail
2024년 8월 18일

유익합니다

답글 달기
comment-user-thumbnail
2024년 8월 27일

2탄부탁드립니다!!

답글 달기