pile·
프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

핵심 포인트
  • 웹에서도 브릿지 객체를 만들되 내부를 빈 구현체로 채워 호출부에서 환경 분기 없이 동일한 함수를 사용할 수 있게 했다.
  • iOS(webkit.messageHandlers.postMessage)와 안드로이드(앱 주입 객체 직접 호출) 규약 차이를 단일 postMessage 함수 내부에 캡슐화했다.
  • 앱에만 있는 기능(사진첩, 토큰 요청 등)과 양쪽에 대응이 있는 기능(새 창, 사진 고르기 등)을 명확히 계층 분리했다.
  • TypeScript로 브릿지 핸들러 이름을 타입으로 정의해 지원하지 않는 이름 사용 시 컴파일 오류로 문자열 오타를 방지했다.
  • 화면 코드에서 if(isApp) 분기가 완전히 사라져 초기화·기능 호출 코드가 환경 무관하게 동일한 형태가 됐다.
상세 정리
  • 문제 발생: 사진 첨부 등 기능에서 웹은 파일 선택창, 앱 웹뷰는 네이티브 사진첩을 사용해야 해 모든 화면에 if(isApp) 분기가 반복.
  • 웹-앱 통신 원리: 앱은 evaluateJavaScript(iOS)/evaluateJavascript(Android)로 웹에 JS 코드를 강제 실행하고, 결과 반환은 window에 등록된 전역 함수로 받는 비동기 구조.
  • 기존 대안 비교: 호출부 매번 분기(코드 반복)·환경별 함수 별도 생성(여전히 환경 인식 필요)·웹에서 브릿지 흉내(규약 한 벌 더 필요) 세 방법 모두 "어딘가는 환경을 알아야 한다"는 한계.
  • 해결 방향: 분기를 호출부에서 최대한 아래로 내리기 위해 웹에도 브릿지 객체를 만들되 내부를 빈 구현체로 채우는 방식 선택.
  • 모듈 스코프 초기화: 컴포넌트 안에 두면 리렌더링마다 재등록되어 모듈 스코프 isInitialized 플래그로 한 번만 등록.
  • 계층 분리 기준: "웹에도 대응하는 게 있는가"로 구분해 앱 전용 기능은 웹에서 조용히 무시, 양쪽 기능은 실제 동작 구현.
  • 네비게이션 추상화: 앱 화면 이동 주소가 복잡한 문자열이라 goHome()·goLogin()·openWebView() 등 의미 있는 이름으로 래핑해 오타 방지.
  • iOS vs 안드로이드 규약 차이: iOS는 undefined 대신 null 명시 필요, 일부 boolean 값이 시뮬레이터에서 문자열로 보내야 통과하는 등 실제 부딪힌 규약 차이를 postMessage 함수 내부에서 흡수.
  • 타입 안전성: 브릿지 핸들러 이름을 TypeScript 유니온 타입으로 정의해 지원 목록 외 이름 사용 시 컴파일 에러.
  • 웹 환경 처리: 웹에서 앱 전용 기능 호출 시 console.warn만 출력하고 에러 없이 통과해 앱용·웹용 초기화 코드를 따로 만들 필요가 없음.
왜 읽나하이브리드 앱(iOS·Android 웹뷰 + 웹 브라우저) 환경에서 통일된 브릿지 API를 설계하는 프론트엔드 개발자에게 구체적인 구현 패턴과 실제 시행착오를 제공한다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·토스 SLASH토스 SLASH·

    모노리포 희망편, 절망의 리포가 희망의 리포로 부활하기까지 걸린 1년

    토스 프론트엔드 팀이 100명 이상의 개발자가 사용하는 모노리포에서 겪은 의존성 버전 불일치, 개발 환경 편차, 느린 설치 속도 문제를 pnpm Catalog 도입으로 해결한 1년간의 여정을 다룬다. 카탈로그로 라이브러리 버전을 중앙화한 결과 의존성 설치 시간을 52% 단축하고 .pnp.cjs 파일 크기를 84% 줄이는 데 성공했다.

    요약 이어보기
    #react#pnpm#monorepo+1