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

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

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

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

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

# Решение для React

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

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

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

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

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

  render() {
    const TagName = this.components[this.props.tag || 'foo']
    return <TagName />
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

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

export default class App extends Component {
  render() {
    return <CustomComponent tag="foo" />
  }
}
1
2
3
4
5
6
7
8

# Решение для 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 },
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

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