Vue.js 렌더 함수의 h(createElement) 파라미터와 함수형 컴포넌트 심화 가이드

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 생태계에서 hcreateElement의 별칭으로 사용하는 것은 일반적인 관례이며, 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
    );
  }
});

태그: Vue.js Render Function createElement Functional Component JSX

7월 15일 23:11에 게시됨