Jiseong's FE Dev blog
RadixUI의 asChild, BaseUI의 render 본문
Intro
최근 Shadcn은 BaseUI를 도입했다.
BaseUI의 멤버는 Radix, Floating UI, Material UI 의 creator 들이다.
BaseUI는 RadixUI보다 더 복잡한 컴포넌트를 제공한다. (ComboBox, Autocomplete, etc..)
7명의 멤버가 full-time으로 관리하고 있다는 점도 강조한다.
RadixUI와 BaseUI 사이 가장 눈에 띈 차이점은 컴포넌트를 composition하는 방식이 다르다는 점이었다.
RadixUI는 asChild 패턴을 대중화시켰다.
많은 라이브러리가 이 패턴을 도입하였다.
BaseUI는 비교적 최근 개발된 라이브러리이고 왜 asChild 방식을 채택하지 않았는지 궁금증이 생겼다.
BaseUI Issue 게시판에 이와 관련된 흥미로운 주제가 올라왔다.
Deprecate render prop and use asChild with children instead #3983
이 글에서는 위 이슈에서 언급된 여러 이유들을 정리해본다.
Radix asChild vs BaseUI render
먼저 일반적으로 component composition을 할 때, 코드를 비교해보자.
function RadixUIExample() {
return (
<Button asChild>
<a href="/profile">Go to profile</a>
</Button>
);
}
function BaseUIExample() {
return (
<Button
render={(props) => (
<a {...props} href="/profile">
Go to profile
</a>
)}
/>
);
}
처음 이 패턴을 봤을 때, 개인적인 생각으론 asChild가 더 심플하다고 느꼈다.
하지만 asChild에 너무 익숙해진 탓이 아닐까도 생각했다.
render prop 의 문제점
이슈에서 제안하는 render 대신 asChild 사용에 대한 주장을 정리해보자.
type safe 하지 않은 코드
- No type safety, contradictory state, this is valid tsx code:
<Badge render={<code>Will this be rendered?</code>}>
<div>Or maybe this? Who knows...</div>
</Badge>
위 코드만 봐서는 code가 렌더링될 지, div가 렌더링될 지 예측하기 힘들다. 실제로는 code가 렌더링된다. 그냥 우선순위가 그러하다. 학습하지 않은 개발자는 어떤 것이 우선순위를 가질지 모른다. 반면 asChild를 사용한다면 타입 에러를 일으켜 문제를 조기에 찾을 수 있다. children prop이 중복으로 2개 넘겨지면서 ts error를 일으킨다.
<Badge asChild children={<code>Will this be rendered?</code>}>
<div>Or maybe this? Who knows...</div>
</Badge>
asChild 라는 패턴을 학습하지 않은 개발자라면 RadixUI를 사용했더라도 예측하기 힘들거라는 답변이 달렸다. 학습하지않은 상태라면 어떤 패턴이든 예측이 어려울 것 같다. 이는 라이브러리의 러닝 커브에 관한 부분이라 생각하고 어쩔 수 없는 영역인 것 같다. 하지만 type-safe 관점에서는 한 번도 생각해 본적 없다. type-safe 관점에서는 확실히 asChild 패턴이 이점을 가지는 것 같다.
가독성의 문제
- More cluttered jsx, the render prop make jsx shift more to the right with formatting.
- More cognitive load, with asChild prop and children it is more natural read and understand jsx.
<Button asChild>
<a href="/profile">Go to profile</a>
</Button>
<Button
render={(props) => (
<a {...props} href="/profile">
Go to profile
</a>
)}
/>
더 복잡한 jsx, 더 복잡한 formating을 만든다. 따라서 jsx를 이해하는데 더 복잡하다. 한마디로 가독성이 떨어진다고 주장한다.
나도 처음 render 방식의 코드를 보고 든 생각이 이 부분이다. 들여쓰기가 많아지고 가독성에서 차이가 보인다.
BaseUI는 왜 render prop 방식을 택했을까?
Base UI는 Radix의 asChild 패턴 대신, render prop 기반의 API를 선택했다.
이는 단순한 취향 문제가 아니라 DX, 성능, API 일관성을 종합적으로 고려한 결정이다.
더 간결한 jsx
// render
<Menu.Item render={<MyMenuItem />} />
// asChild
<Menu.Item asChild>
<MyMenuItem />
</Menu.Item>
불필요한 줄 수를 줄이고, 트리 구조를 단순하게 나타낸다고 주장한다.
render 함수는 더 명확하고 빠르고 단순하다.
asChild는 boolean인 반면 render를 사용하면 의도를 더 명확히 전달 할 수 있다.
또, asChild는 내부적으로 React.cloneElement에 의존하는 경우가 많다.
legaycy API에 의존하면 성능적으로 느려질 수 있다고 주장한다.
개발자의 실수를 줄인다.
asChild는 깜빡하는 경우가 있다. 이는 nested button 등의 문제로 이어질 수 있다.
render prop을 통해 children을 일관성 있게 처리한다면 이러한 실수를 막고 DX를 높인다고 주장한다.
느낀점
Issue 작성자가 주장하는 내용, Base UI 팀에서 render prop 방식을 선택한 이유 모두 타당하다고 생각한다.
특히 Issue에서 제안한 type-safe 관점의 문제 제기는 개인적으로 한 번도 깊게 고민해보지 않았던 지점이라 인상 깊었다.
또, BaseUI에서 언급한 cloneElement의 사용 지양에 관한 내용도 인상 깊었다.
이번 이슈와 Base UI의 설계를 살펴보면서, 컴포넌트 API를 설계할 때 단순한 사용성이나 표현력뿐만 아니라 타입 안정성, 일관성, 그리고 실수하기 어려운 구조를 만드는 것이 얼마나 중요한지 다시 한 번 생각하게 되었다.
사소한 DX 관련 내용이지만, 사소한 것들이 모여서 라이브러리의 위상이 높아지는 것 같다.
'React' 카테고리의 다른 글
| Frontend, 완벽한 자동로그인 관리 (0) | 2025.12.26 |
|---|---|
| useEffect 남용을 줄이자. (0) | 2025.03.02 |
| 선언적으로 에러처리 아주 깔끔하게 하기 (Next.js 15) (0) | 2024.10.29 |
| 웹뷰에서 소셜 로그인시 문제 해결하기 (2) | 2024.09.15 |
| 찜 하기, 백엔드 서버 부하 줄이기 (feat. react-query) (0) | 2024.06.09 |