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

항공 프론트엔드 구축기 (2/10): Vue2 디자인 시스템을 React로 옮기기

여기어때 항공 프론트엔드 팀이 Vue2 기반 공용 컴포넌트 라이브러리를 React(Next.js + Tailwind)로 재구현 없이 포팅한 과정을 다룬다. 수치는 하나도 바뀌지 않았고 바뀐 건 프레임워크 관례(케밥케이스→카멜케이스, 이벤트 바인딩) 뿐이었다. 이 포팅이 가능했던 세 가지 구조적 조건과, 그대로 옮기기 어려워 책임 분리를 선택한 사례도 함께 설명한다.

핵심 포인트
  • 버튼 크기별 수치(모서리 8/8/10/10, 좌우 여백 12/14/18/20, 높이 32/40/48/56)가 하나도 바뀌지 않았다. 바뀐 건 토큰 이름 표기법(PascalCase→kebab-case)과 임의값이 스케일값으로 된 것뿐이다.
  • 포팅이 가능했던 세 조건: 프레임워크 종속 UI 라이브러리 미사용, 디자인 토큰이 Tailwind 설정에 정의됨, 양쪽 다 Tailwind 사용.
  • 여백 prop을 없애고 className으로 통일해 "컴포넌트마다 여백 prop 이름을 외워야 하는" 문제를 해결했다. 내부 padding 덮어쓰기는 열어 두되 개발자 판단에 맡겼다.
  • 입력 필드의 포맷팅 로직(카드번호, 주민번호 등 11가지 타입)은 컴포넌트 밖으로 이동해 컴포넌트가 도메인 지식을 모르게 했다. 표준 HTML input 타입 5가지만 남겼다.
  • 포팅 과정에서 수년 운영된 코드의 버그 2개(prefix 누락 스피너, 클래스 누적 점 인디케이터)가 부수적으로 발견됐다.
상세 정리
  • 배경: 합친 두 팀이 항공 서비스를 React로 새로 만들면서 기존 Vue2 공용 라이브러리의 수십 개 컴포넌트를 어떻게 할지 결정해야 했다. 일정상 재구현은 불가능했다.
  • 수치 동일성 확인: 버튼 타이포그래피 플러그인을 양쪽 대조 시 fontSize/lineHeight 동일, 색상 토큰도 동일. 클래스 문자열 자체가 이식 단위였다.
  • 포팅 조건 1: 프레임워크 종속 UI 라이브러리 미사용. 외부 Vue UI 라이브러리를 안 쓰고 직접 만든 컴포넌트라 실체가 코드에 있었다.
  • 포팅 조건 2: 디자인 토큰이 Tailwind 설정에 있었다. 간격 스케일이 1~999를 그대로 픽셀로 매핑하는 동일한 규칙이라 p-16이 양쪽 모두 16px.
  • 포팅 조건 3: 양쪽 다 Tailwind. 클래스 문자열이 프레임워크를 타지 않아 문자열 자체를 옮겼다.
  • 여백 API 변경: Vue2는 hasPaddingTop같은 boolean prop이 컴포넌트마다 달라 외워야 했다. React에서는 className="yf-mt-12 yf-mb-12"으로 통일. 안쪽 여백 덮어쓰기는 막지 않았다.
  • TextField 리설계: Vue2에서 type="card-number" 등 11가지 타입을 컴포넌트 안에서 처리하고 전역 유틸($punycode, $numWithCommas)을 호출했다. React에서는 formatter prop으로 화면이 직접 정규식을 넘기는 방식으로 전환. 표준 HTML 타입 5개(text/number/password/tel/email)만 남겼다.
  • 전역 플러그인 문제: $listeners 기반 tabindex 제어가 {...rest} 전개와 함께 조용히 사라졌다. "안 옮긴 게 아니라 옮기려던 것이 빠진 경우"로 코드만 봐서는 구분 불가.
  • 부수 발견: 스피너의 루트 엘리먼트에 prefix 없는 클래스(flex → 아무 역할 안 함), 점 인디케이터의 클래스 누적 버그(클래스 병합 유틸이 마지막에 정리해줘서 정상 동작처럼 보였음).
왜 읽나프레임워크 마이그레이션 시 디자인 시스템을 재구현 없이 포팅하려는 팀에게, 포팅이 가능한 구조적 조건과 책임 분리로 "그대로 옮기기 어려운 것"을 처리하는 방법을 제공한다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

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

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

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

    요약 이어보기
    #react#typescript#webview+2