Vue.js 애플리케이션에서 가상 DOM(Virtual DOM)을 직접 제어해야 할 때 render 함수를 사용하며, 이 과정에서 h라는 매개변수를 자주 마주하게 됩니다. 다음과 같은 라우터 뷰 래퍼 컴포넌트 정의가 대표적인 예시입니다.
const RouteWrapper = {
render: h => h("router-view")
};
이 코드의 기본 원리는 다음과 같이 createElement 함수를 호출하는 것과 동일합니다.
const Layout = {
render: function (createElement) {
return createElement(MainApp);
}
};
ES6 단축 메서드 표기법을 적용하면 다음과 같이 작성할 수 있습니다.
const Layout = {
render(createElement) {
return createElement(MainApp);
}
};
Vue 생태계에서 h를 createElement의 별칭으로 사용하는 것은 일반적인 관례이며, JSX 문법에서도 이를 필수로 요구합니다. Vue용 Babel 플러그인 3.4.0 버전부터는 ES2015 문법으로 선언된 JSX 포함 메서드와 getter에 const h = this.$createElement를 자동으로 주입해 줍니다. 따라서 매개변수로 (h)를 명시적으로 선언하지 않아도 됩니다. (단, 화살표 함수나 일반 함수 내부에서는 자동 주입되지 않으므로 주의해야 합니다.)
이 관례를 적용하여 코드를 간결하게 변경하면 다음과 같습니다.
const AppShell = {
render(h) {
return h(MainApp);
}
};
화살표 함수를 활용하면 더욱 짧게 표현할 수 있습니다.
const AppShell = {
render: h => h(MainApp)
};
함수형 컴포넌트 (Functional Components)
내부 상태를 관리하지 않고, 수명 주기 훅을 사용하지 않으며, 단순히 전달받은 Props에 기반하여 UI만 출력하는 컴포넌트가 있습니다. 이러한 경우 functional: true 옵션을 사용하여 함수형 컴포넌트로 선언할 수 있습니다. 함수형 컴포넌트는 반응형 데이터가 없으며 this 컨텍스트(인스턴스)를 갖지 않는 순수 함수와 같습니다.
Vue.component('DynamicHeading', {
functional: true,
props: {
level: {
type: Number,
default: 1
}
},
// 인스턴스가 없으므로 두 번째 매개변수인 context를 통해 데이터에 접근
render: function (createElement, context) {
return createElement(
'h' + context.props.level,
context.children
);
}
});
Vue 2.5.0 이상 버전에서 단일 파일 컴포넌트(SFC)를 사용한다면, 템플릿 기반의 함수형 컴포넌트를 다음과 같이 선언할 수 있습니다.
<template functional>
<!-- 템플릿 내용 -->
</template>
함수형 컴포넌트에서 필요한 모든 데이터는 context 매개변수를 통해 전달됩니다. 이 객체는 다음과 같은 필드를 포함합니다.
- props: 모든 Props를 포함하는 객체
- children: VNode 자식 노드 배열
- slots: 모든 슬롯을 포함하는 객체를 반환하는 함수
- scopedSlots: (2.6.0+) 스코프 슬롯을 노출하는 객체 (일반 슬롯도 함수 형태로 노출)
- data: 컴포넌트에 전달된 전체 데이터 객체 (
createElement의 두 번째 인수로 전달됨) - parent: 부모 컴포넌트에 대한 참조
- listeners: (2.3.0+) 부모 컴포넌트가 현재 컴포넌트에 등록한 모든 이벤트 리스너 객체 (
data.on의 별칭) - injections: (2.3.0+)
inject옵션을 사용할 경우 주입되어야 할 속성을 포함하는 객체
함수형 컴포넌트는 오직 함수로만 동작하기 때문에 렌더링 오버헤드가 매우 낮습니다. 또한, 다음과 같은 상황에서 래퍼(Wrapper) 컴포넌트로 활용하기에 매우 유용합니다.
- 조건에 따라 여러 컴포넌트 중 하나를 선택하여 프로그래밍 방식으로 렌더링
- 자식 컴포넌트로 전달하기 전에 children, props, data를 조작하거나 필터링
다음은 전달받은 데이터의 구조와 상태에 따라 적절한 뷰 컴포넌트를 동적으로 선택하여 렌더링하는 AdaptiveList 컴포넌트 예시입니다.
const EmptyState = { /* ... */ };
const DataTable = { /* ... */ };
const SortedList = { /* ... */ };
const BasicList = { /* ... */ };
Vue.component('AdaptiveList', {
functional: true,
props: {
records: {
type: Array,
required: true
},
isSorted: Boolean
},
render: function (createElement, context) {
function resolveComponent() {
const items = context.props.records;
if (items.length === 0) return EmptyState;
if (typeof items[0] === 'object') return DataTable;
if (context.props.isSorted) return SortedList;
return BasicList;
}
return createElement(
resolveComponent(),
context.data,
context.children
);
}
});