---
type: snippet
tags: ['react', 'as', 'as-child', 'render-prop', 'polymorphic']
status: release
ctime: 2026-01-26
mtime: 2026-01-26
---

컴포넌트가 렌더링하는 요소를 외부에서 제어하는 세 가지 패턴.

## as

태그명을 props로 전달. 가장 단순하지만 타입이 복잡해짐.

```tsx
type PolymorphicProps<E extends React.ElementType> = {
  as?: E
} & React.ComponentPropsWithoutRef<E>

function Button<T extends React.ElementType = 'button'>({
  as,
  ...props
}: PolymorphicProps<T>) {
  const Comp = as || 'button'
  return <Comp {...props} />
}
```

## asChild + Slot

Radix UI 방식. 자식 요소로 렌더링 위임. prop 병합이 암묵적.

```tsx
import { Slot } from '@radix-ui/react-slot'

function Button({ asChild, ...props }) {
  const Comp = asChild ? Slot : 'button'
  return <Comp {...props} />
}
```

Slot이 자동으로 처리하는 것: className 병합, 이벤트 핸들러 합성, ref 병합.

Slot 직접 구현:

```tsx
const Slot = forwardRef(({ children, ...props }, ref) => {
  if (!isValidElement(children)) return null

  return cloneElement(children, {
    ...props,
    ...children.props,
    ref: ref || children.ref,
  })
})
```

자식 요소 타입에 따른 조건부 Props:

```tsx
type ConditionalProps<T> = T extends ReactElement<any, 'a'>
  ? { href?: string; external?: boolean }
  : T extends ReactElement<any, 'button'>
    ? { type?: 'button' | 'submit' }
    : {}
```

## render

Base UI, React Aria 방식. 명시적 prop 전달. 타입 추론이 가장 좋음.

```tsx
// Element 방식
<Button render={<a href="/about" />}>Link</Button>

// Callback 방식 - state 접근 가능
<Button render={(props, state) => (
  <motion.button {...props} animate={state.isPressed ? ... : ...} />
)}>
  Animated
</Button>
```

render prop은 `(domProps, renderProps)` 시그니처. domProps는 ref 포함 DOM 속성, renderProps는 컴포넌트 상태(isPressed, isSelected 등).

## 선택 기준

```
| 패턴    | 복잡도 | 타입 안전성 | 유연성 |
| ------- | ------ | ----------- | ------ |
| as      | 낮음   | 보통        | 낮음   |
| asChild | 중간   | 보통        | 높음   |
| render  | 중간   | 높음        | 높음   |
```

- 단순 태그 변경 → `as`
- 기존 컴포넌트 합성 → `asChild` 또는 `render`
- 상태 기반 커스터마이징 → `render`

---

- [Base UI useRender](https://base-ui.com/react/utils/use-render) - render prop 커스텀 훅 구현
- [React Aria render prop PR](https://github.com/adobe/react-spectrum/pull/9499) - React Aria Components에 render prop 도입
- [Slot/asChild Pattern](https://boda.sh/blog/react-slot-aschild-pattern) - Radix Slot vs Base UI render 비교
- [React as prop (christianvm)](https://www.christianvm.dev/blog/react-as-prop) - ElementType, ComponentPropsWithoutRef 타입 활용
- [slot-jsx-pragma](https://github.com/jjenzz/slot-jsx-pragma) - cloneElement 없이 asChild 구현하는 JSX pragma
- [React as prop (robinwieruch)](https://www.robinwieruch.de/react-as-prop/) - 디자인 시스템에서 시맨틱 HTML 유지하며 스타일 분리
- [asChild in React, Svelte, Vue, Solid](https://medium.com/@bryanmylee/aschild-in-react-svelte-vue-and-solid-for-render-delegation-645c73650ced) - 프레임워크별 render delegation 비교
- [React asChild (jacobparis)](https://www.jacobparis.com/content/react-as-child) - asChild 패턴 구현 튜토리얼
- [Polymorphism done well](https://www.kripod.dev/blog/behind-the-as-prop-polymorphism-done-well/) - 타입 안전한 polymorphic 컴포넌트
