Динамические теги в React и Vue

В мире фронтенда принято переиспользовать один и тот же компонент множество раз. BEM-блоки, компонентный подход – всё об этом. Это как возвести здание из кирпичиков.

Допустим, нам нужно использовать одну и ту же функциональность, но при этом иметь возможноть задавать компоненту разные html-теги. И тогда хочется сделать что-то подобное:

render() {
  return <[this.props.tag] />
}

Передавать через props нужный тег. Без жёсткой привязки. Динамически.

Решение для React

В JSX данный выше код вызовет ошибку на этапе компиляции. Дело в том, что нам требуются имена элементов, начинающиеся с заглавной буквы. То есть подойдёт следующая запись:

render() {
  const TagName = this.props.tag
  return <TagName />
}

Альтернативный способ потребует импорта компонентов:

import React, { Component } from 'react'
import FooComponent from './foo-component'
import BarComponent from './bar-component'

export class CustomComponent extends Component {
  components = {
    foo: FooComponent,
    bar: BarComponent
  }

  render() {
    const TagName = this.components[this.props.tag || 'foo']
    return <TagName />
  }
}

Подключаем извне как обычно:

import React, { Component } from 'react'
import { CustomComponent } from './custom-component'

export class App extends Component {
  render() {
    return <Custom-Component tag="foo" />
  }
}

Решение для Vue.js

С Vue.js в этом плане тоже всё отлично. И для этого также используется функция render.

import Vue from 'vue'

Vue.component('custom-component', {
  name: 'custom-component',

  render(createElement) {
    return createElement(
      this.tag,           // tag
      this.$slots.default // array of children
    )
  },

  props: {
    tag: { type: String, required: true },
  }
})

Готово. Теперь можно переиспользовать и задавать свои html-теги.

<custom-component tag="section">
  <p>Hello</p>
</custom-component>