Radix UI의 특별한 prop, asChild

PSSGYT·2025년 8월 5일

asChild의 역할

asChild={true}일 때:

  • Radix UI 컴포넌트가 자체 DOM 요소를 렌더링하지 않음
  • 대신 자식 요소의 props를 받아서 그 자식 요소에 Radix UI의 기능을 전달
  • 자식 요소가 실제 DOM 요소가 됨

asChild={false} 또는 생략할 때:

  • Radix UI 컴포넌트가 자체 DOM 요소를 렌더링
  • 추가적인 wrapper div가 생성됨

예시

// asChild={true} - Button이 실제 DOM 요소가 됨
<DialogTrigger asChild>
  <Button>열기</Button>
</DialogTrigger>

// asChild 없음 - 추가 div가 생성됨
<DialogTrigger>
  <Button>열기</Button>
</DialogTrigger>

차이점

  • asChild 있음: <button>열기</button> (깔끔함)
  • asChild 없음: <div><button>열기</button></div> (불필요한 wrapper)

따라서 asChild를 사용하면 불필요한 DOM 요소 없이 깔끔한 HTML 구조를 만들 수 있음


HTML 중첩 문제

  • 오류 코드
    
    dialog.tsx:38 <button> cannot contain a nested <button>.
    
    See this log for the ancestor stack trace.
    
    react-dom-client.development.js:4507 Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
    
    - A server/client branch `if (typeof window !== 'undefined')`.
    - Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
    - Date formatting in a user's locale which doesn't match the server.
    - External changing data without sending a snapshot of it along with the HTML.
    - Invalid HTML tag nesting.
    
    It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
    
    https://react.dev/link/hydration-mismatch
    
    at throwOnHydrationMismatch (react-dom-client.development.js:4507:11)
    
    at beginWork (react-dom-client.development.js:10928:17)
    
    at runWithFiberInDEV (react-dom-client.development.js:872:30)
    
    at performUnitOfWork (react-dom-client.development.js:15677:22)
    
    at workLoopConcurrentByScheduler (react-dom-client.development.js:15671:9)
    
    at renderRootConcurrent (react-dom-client.development.js:15646:15)
    
    at performWorkOnRoot (react-dom-client.development.js:14940:13)
    
    at performWorkOnRootViaSchedulerTask (react-dom-client.development.js:16766:7)
    
    at MessagePort.performWorkUntilDeadline (scheduler.development.js:45:48)
- 원인 ⇒ DialogTrigger 안에 Button 컴포넌트를 사용할 때 발생하는 HTML 중첩 문제
    
    = DialogTrigger는 이미 <code>button</code> 요소를 렌더링하는데, 그 안에 또 다른 <code>button</code> 요소인 Button 컴포넌트가 들어가서 발생하는 문제
    
- 해결 방법 ⇒ DialogTrigger에 asChild prop을 사용
    - 코드
        
        ```tsx
        <Dialog>
        	<DialogTrigger asChild>
        		<Button>다이얼로그 열기</Button>
        	</DialogTrigger>
        	<DialogContent>
        		<DialogHeader>
        			<DialogTitle>테스트 다이얼로그</DialogTitle>
        			<DialogDescription>이것은 Dialog 컴포넌트의 동작을 테스트하는 예시입니다.</DialogDescription>
        		</DialogHeader>
        		<div className="mt-4">
        			<p>여기에 원하는 내용을 넣을 수 있습니다.</p>
        		</div>
        		<DialogFooter>
        			<DialogClose asChild>
        				<div className="flex gap-2">
        					<Button variant="primary" outline={true}>
        						취소
        					</Button>
        					<Button variant="primary">확인</Button>
        				</div>
        			</DialogClose>
        		</DialogFooter>
        	</DialogContent>
        </Dialog>
        ```
        
    
 ### 주요 변경사항:
    
  1. **DialogTrigger에 asChild 추가**: 이렇게 하면 DialogTrigger가 자체 <code>button</code>을 렌더링하지 않고, 자식 요소(Button)를 그대로 사용함
    2. **DialogClose에도 asChild 추가**: 마찬가지로 닫기 버튼도 중첩을 방지함
    3. **Button 컴포넌트 사용**: 커스텀 <code>button</code> 대신 프로젝트의 <code>Button</code> 컴포넌트를 사용
    
    이렇게 하면 HTML 중첩 문제가 해결되고 hydration 오류가 발생하지 않음
profile
pasongsong gayeon tak!

0개의 댓글