React 19 시대, 컴포넌트를 어떻게 설계해야 하는가

React 19 시대, 컴포넌트를 어떻게 설계해야 하는가

새 훅을 전부 쓰는 것도, 공통 컴포넌트를 무작정 늘리는 것도 답이 아니다—React 19와 헤드리스 패턴이 함께 가리키는 설계 원칙.

React 19 컴포넌트 설계 헤드리스 컴포넌트 useOptimistic useActionState 공통 컴포넌트 Server Components 프론트엔드 아키텍처
광고

React 19가 바꾼 것, 그리고 바꾸지 않은 것

React 19가 정식 출시됐을 때, 커뮤니티 반응은 두 가지였다. "이 훅들을 당장 다 써야 한다"는 흥분과, "어차피 곧 또 바뀌겠지"라는 피로. 그런데 dev.to에 올라온 실무 회고를 보면, 정답은 그 중간 어딘가에 있다. useOptimistic, useActionState, use() 훅—이 셋은 실제로 UX 버그를 줄이고 보일러플레이트를 걷어냈다. 반면 React Compiler(구 Forget)와 클라이언트 트리에서의 Document Metadata는 생태계가 따라올 때까지 기다리는 편이 낫다. 새 기능을 전부 채택하는 것이 실력이 아니라, 어떤 문제를 해결하는지 먼저 묻는 것이 실력이다.

세 가지 훅이 실무에서 실제로 하는 일

useOptimistic이 해결하는 건 낙관적 업데이트의 롤백 누락이다. 기존 패턴에서 useState로 직접 낙관적 상태를 만들면, try-catch 블록에서 롤백 코드를 빠뜨리는 실수가 잦았다. useOptimistic은 이 롤백 경로를 구조적으로 강제한다. 장바구니 수량 변경, 좋아요 버튼처럼 지연이 체감되는 모든 뮤테이션에 즉시 적용할 수 있다.

useActionStateuseFormStatus는 폼의 pending 상태 관리를 선언형으로 바꾼다. 마케팅 페이지 뉴스레터 폼 하나에도 이전엔 isPending 플래그를 직접 들고 다녀야 했는데, 이제는 [state, formAction, pending]만 꺼내 쓰면 된다. Next.js 15 Server Actions와 조합하면 별도 API 라우트 파일 없이 서버 뮤테이션이 완성된다—단, 클라이언트 입력값은 항상 불신해야 한다는 원칙은 여전히 유효하다.

use() 훅은 패러다임 전환에 가깝다. Server Component에서 생성한 Promise를 Client Component로 내려보내면, use(productsPromise)가 Suspense와 함께 비동기 데이터를 읽어낸다. useEffect로 데이터를 쫓던 사이드 이펙트 스파게티를 걷어낼 수 있다. RSC 스트리밍과 함께 쓸 때 진가가 드러나는 API다.

공통 컴포넌트는 언제부터 팀 생산성을 갉아먹기 시작하는가

카카오페이 팀의 회고는 React 19 훅 이야기와 다른 층위에서 같은 질문을 던진다. 공통 컴포넌트는 처음엔 생산성을 높인다. 재사용을 위해 조금씩 공통화하다 보면 어느 순간 컴포넌트가 세밀해지고, 문서화되지 않은 결정들이 쌓이고, 히스토리를 아무도 온전히 공유하지 못하는 상태가 된다. 새 팀원은 어떤 기준으로 어떤 컴포넌트를 써야 하는지조차 파악하기 어렵다.

CoverageSelectCard 사례가 이 문제를 잘 보여준다. 처음엔 보험 상품 선택이라는 단순한 UI를 위해 만들어진 컴포넌트였는데, 시간이 지나며 바텀시트 열기/닫기, iOS body height 조작, 드래그 인터랙션, 애니메이션 딜레이 제어까지 흡수했다. 새 옵션이 생길 때마다 컴포넌트 자체를 수정해야 하는 상황—이건 재사용 컴포넌트가 아니라 수정 불안 컴포넌트다. "건들면 다 죽을 것 같다"는 감각이 팀 안에 공유되는 순간, 그 컴포넌트는 사실상 죽은 것이다.

공통 컴포넌트의 수명은 세 가지 조건에서 끝난다고 볼 수 있다. 아무도 찾지 않을 때, 외부 정책 변화로 deprecated될 때, 그리고 내부 로직이 너무 복잡해서 아무도 손대지 못할 때. 문제는 이 죽음이 선언되지 않은 채 계속 코드베이스에 남아 있다는 것이다.

헤드리스 패턴이 두 문제를 동시에 푸는 방식

카카오페이 팀이 제안하는 해법은 헤드리스 컴포넌트 패턴이다. 상태 관리, 인터랙션 로직, 접근성 처리만 공통으로 제공하고, 시각적 표현은 사용처에서 결정하도록 위임한다. 이를 기반으로 가이드에 맞는 기본 UI 버전과 서비스 특성에 맞게 커스터마이징 가능한 자유 UI 버전으로 나눠 운영하는 구조를 제안한다. 디자인의 완전한 균일화를 목표로 하지 않는 제품에서 안정성과 접근성은 공통으로 가져가되, 외형은 유연하게 허용하는 방식이다.

이 관점은 React 19의 새 API와 정확히 맞닿아 있다. use() 훅과 Server Component의 조합이 데이터 레이어를 선언형으로 분리하듯, 헤드리스 패턴은 로직 레이어와 UI 레이어를 분리한다. useActionState가 폼의 pending 상태를 컴포넌트 외부에서 제어하도록 설계된 것처럼, 헤드리스 컴포넌트는 상태와 인터랙션을 렌더링과 분리한다. 패턴의 방향이 같다.

React 19 시대 컴포넌트 설계의 실질적 기준

두 실무 회고가 수렴하는 설계 원칙은 단순하다. 첫째, 새 API는 "이미 겪고 있는 문제"를 해결할 때만 도입한다. 낙관적 업데이트 롤백 버그가 없다면 useOptimistic을 서두를 이유가 없다. 둘째, 공통 컴포넌트는 역할이 커질수록 잘게 쪼개야 한다. 한 컴포넌트에 바텀시트 로직과 보험 상품 선택 UI가 공존하는 순간, 그건 공통 컴포넌트가 아니라 서비스 컴포넌트다. 셋째, 문서화되지 않은 결정은 기술 부채보다 빠르게 팀 인지 부하를 높인다. 컴포넌트를 공통화하는 순간, 그 결정의 이유와 경계를 같이 기록해야 한다.

forwardRef를 점진적으로 걷어내되 새 코드부터 ref-as-prop으로 바꾸는 전략, React Compiler는 Next.js 공식 문서가 안정화된 뒤 도입하는 전략—이 "기다림도 전략이다"라는 태도가 실은 공통 컴포넌트 설계에도 그대로 적용된다. 공통화는 두 곳에서 반복되는 패턴이 세 번째 등장할 때 하는 것이다. 미리 공통화하는 것은 설계가 아니라 예측이고, 예측은 틀린다.

앞으로: 컴포넌트 설계의 무게중심은 어디로 가는가

React Compiler가 안정화되면 useMemouseCallback의 수동 관리가 상당 부분 자동화된다. 그 시점에 컴포넌트 설계의 무게중심은 메모이제이션 전략보다 경계 설계—어디까지 Server Component로 두고, 어디서부터 Client Component로 넘길 것인가, 그리고 어떤 로직을 헤드리스로 분리할 것인가—로 이동할 것이다. React 19의 새 훅들은 그 경계를 더 명확하게 그을 수 있는 도구들이다. 도구가 좋아졌다고 설계가 저절로 좋아지지는 않는다. 설계는 여전히 팀이 묻고 기록하고 결정하는 몫이다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요