Render Function과 JSX
Render Function은 템플릿 대신 JavaScript/TypeScript로 직접 VNode를 생성하는 방법입니다. 동적 렌더링이 복잡할 때 템플릿보다 유연합니다.
대부분의 경우 템플릿이 더 적합하지만, 프로그래매틱한 컴포넌트 생성이 필요한 상황에서는 Render Function이 강력합니다.
h() 함수 기본
import { h, ref, defineComponent } from 'vue'
// h(type, props, children)
const vnode = h(
'div', // 타입: 태그명 또는 컴포넌트
{ class: 'container', id: 'app' }, // props
[ // children
h('h1', null, '제목'),
h('p', null, '본문')
]
)
Render Function으로 컴포넌트 작성
import { h, ref, defineComponent } from 'vue'
export default defineComponent({
props: {
level: { type: Number, required: true, validator: (v: number) => v >= 1 && v <= 6 }
},
setup(props, { slots }) {
return () => h(
`h${props.level}`, // h1 ~ h6 동적 생성
{ class: 'heading' },
slots.default?.() // 슬롯 컨텐츠 렌더링
)
}
})
<!-- 사용 -->
<template>
<DynamicHeading :level="2">동적 제목</DynamicHeading>
<!-- 결과: <h2 class="heading">동적 제목</h2> -->
</template>
이런 경우는 템플릿으로 구현하면 v-if가 6개 필요하지만, Render Function으로는 한 줄입니다.
JSX 사용
// vite.config.ts에서 @vitejs/plugin-vue-jsx 플러그인 필요
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
return () => (
<div class="counter">
<p>카운트: {count.value}</p>
<button onClick={() => count.value++}>증가</button>
</div>
)
}
})
JSX에서의 Vue 기능
import { defineComponent, ref, withModifiers } from 'vue'
export default defineComponent({
setup() {
const items = ref(['A', 'B', 'C'])
const show = ref(true)
return () => (
<div>
{/* 조건부 렌더링 — 삼항 또는 && */}
{show.value && <p>보이는 텍스트</p>}
{/* 리스트 렌더링 — map */}
<ul>
{items.value.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
{/* 이벤트 수식어 — withModifiers */}
<form onSubmit={withModifiers(() => {
console.log('제출')
}, ['prevent'])}>
<button type="submit">제출</button>
</form>
{/* v-model은 JSX에서 미지원 — 수동 바인딩 */}
<input
value={text.value}
onInput={(e: Event) => {
text.value = (e.target as HTMLInputElement).value
}}
/>
</div>
)
}
})
템플릿 vs Render Function vs JSX
| 항목 | 템플릿 | Render Function | JSX |
|---|---|---|---|
| 가독성 | 높음 | 낮음 | 중간 |
| 컴파일러 최적화 | 자동 적용 | 수동 | 부분 적용 |
| 유연성 | 제한적 | 최고 | 높음 |
| TypeScript | 제한적 | 완벽 | 완벽 |
| 적합한 경우 | 대부분 | 동적 렌더링 | React 경험자 |
Render Function에서 슬롯
import { h, defineComponent } from 'vue'
export default defineComponent({
setup(props, { slots }) {
return () => h('div', { class: 'card' }, [
// 이름 있는 슬롯 렌더링
slots.header && h('div', { class: 'header' }, slots.header()),
// 기본 슬롯
h('div', { class: 'body' }, slots.default?.()),
// 스코프드 슬롯 — 데이터 전달
slots.footer && h('div', { class: 'footer' },
slots.footer({ year: new Date().getFullYear() })
)
])
}
})
실전 활용 — 아이콘 컴포넌트
import { h, defineComponent } from 'vue'
const icons = {
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
search: 'M15.5 14h-.79l-.28-.27...',
close: 'M19 6.41L17.59 5 12 10.59...'
}
export default defineComponent({
props: {
name: { type: String as () => keyof typeof icons, required: true },
size: { type: Number, default: 24 }
},
setup(props) {
return () => h('svg', {
width: props.size,
height: props.size,
viewBox: '0 0 24 24',
fill: 'currentColor'
}, [
h('path', { d: icons[props.name] })
])
}
})
면접 팁
- "언제 Render Function을 쓰나요?" — 동적 태그 생성, 프로그래매틱 컴포넌트, 라이브러리 개발처럼 템플릿의 선언적 문법이 오히려 복잡해지는 경우
- 템플릿의 컴파일러 최적화(PatchFlags, 정적 호이스팅)가 Render Function에서는 자동 적용되지 않는 점을 알고 있으면 좋습니다
- React 경험자라면 JSX가 익숙하겠지만, Vue에서는 ** 템플릿을 기본으로** 사용하는 것이 커뮤니티 컨벤션입니다
요약
Render Function은 h() 함수로 VNode를 직접 생성하여 템플릿보다 유연한 렌더링이 가능합니다. JSX는 Render Function의 문법 설탕으로 가독성을 높여줍니다. 대부분의 경우 템플릿이 적합하지만, 동적 컴포넌트 생성이나 라이브러리 개발에서는 Render Function이 빛을 발합니다.
댓글 로딩 중...