Как правильно создать компонент для генерации других компонентов vue3

Пытаюсь сделать один компонент, который будет генерировать разные ui компоненты, такие как input, checkbox, radiobutton и прочие элементы.

Суть такая, на вход компоненту в качестве пропсов передаю информацию о нужном элементе, в ответ получаю сгенерированный код

пример вызова

export default {
  ...
  data() {
    return {
        fields: {
            NAME: {
                name: 'name',
                type: 'text',
                label: 'Имя',
                require: true,
            },
            CALENDAR: {
                name: 'calendar',
                type: 'calendar',
                label: 'Дата начала',
                calendar: {
                    minDate: new Date()
                }
            }
        }
    }
}

Вызов компонента генератора

<template v-for="field in fields" :key="field.name">
   <div :class="field?.width ?? ''">
      <v-input-group :field="field" />
   </div>
</template>

Компонент для генерации элементов

<template>
  <template v-if="field.active">
    <template v-if="['text', 'email', 'password'].includes(field.type)">
      <InputWrapper 
        :class="field.classWrap ?? ''"
        :info="field.info ?? ''"
        :helper="field.helper" 
        :warning="field.warning"
        :error="field.error">
        <Input 
          v-model="field.value"
          :warning="field.warning" 
          :error="field.error" 
          :tooltip="field.tooltip"
          :label="field.label" 
          :label-inside="field.labelInside ?? false"
          :disabled="field.disabled ?? false"
          :type="field.type"
          :require-show="field.requireShow ?? false"
        />
      </InputWrapper>
    </template>
    <template v-else-if="['calendar'].includes(field.type)">
      <InputWrapper 
        :field-type="field.type"
        :class="field.classWrap ?? ''"
        :info="field.info ?? ''"
        :helper="field.helper" 
        :warning="field.warning"
        :error="field.error">
        <Popper 
          arrowPadding="16"
          arrow 
          :show="field?.calendar?.show" 
          v-click-outside="() => field.calendar.show = false"
        >
          <Input 
            v-model="field.value"
            :warning="field.warning" 
            :error="field.error" 
            :tooltip="field.tooltip"
            :label="field.label" 
            :label-inside="field.labelInside ?? false"
            :disabled="field.disabled ?? false"
            :type="field.type"
            :require-show="field.requireShow ?? false"
            :mask="field?.mask?.mask ?? ''"
            :mask-token="field?.mask?.token ?? ''"
            :calendar="field?.calendar"
          />
          <template #content>
            <DatePicker 
              ref="calendar"
              :mode="'date'"
              :update-on-input="true"
              :min-date="field.calendar.minDate ?? null"
              :max-date="field.calendar.maxDate ?? null"
              borderless 
              title-position="left" 
              v-model="field.calendar.value"
            />
          </template>
        </Popper>
      </InputWrapper>
    </template>
</template>

В компоненте генерации может быть в дальнейшем много различных типов полей.

В процессе разработки заметил, что некоторые комопненты генерируются очень долго, например календарь

введите сюда описание изображения

Вне компонента он грузится быстрее.

подскажите, насколько правильно я сделал генератор, какие есть еще варианты разработки такого мультифункционального компонента без потери в производительности ?

UPD Код компонента генератора

<template>
  <template v-if="field.active">
    <template v-if="['text', 'email', 'password'].includes(field.type)">
      <InputWrapper 
        :class="field.classWrap ?? ''"
        :info="field.info ?? ''"
        :helper="field.helper" 
        :warning="field.warning"
        :error="field.error">
        <Input 
          v-model="field.value"
          :warning="field.warning" 
          :error="field.error" 
          :tooltip="field.tooltip"
          :label="field.label" 
          :label-inside="field.labelInside ?? false"
          :disabled="field.disabled ?? false"
          :type="field.type"
          :require-show="field.requireShow ?? false"
          :mask="field?.mask?.mask ?? ''"
          :mask-token="field?.mask?.token ?? ''"
          :mask-detail="field?.mask?.maskDetail ?? false"
          @raw-value="(v) => { if (field?.mask?.raw !== undefined) field.mask.raw = v }"
          @mask-complete="(v) => { if (field?.mask?.complete !== undefined) field.mask.complete = v }"
        />
      </InputWrapper>
    </template>
    <template v-else-if="['calendar'].includes(field.type)">
      <InputWrapper 
        :field-type="field.type"
        :class="field.classWrap ?? ''"
        :info="field.info ?? ''"
        :helper="field.helper" 
        :warning="field.warning"
        :error="field.error">
        <Popper 
          arrowPadding="16"
          arrow 
          :show="field?.calendar?.show" 
          v-click-outside="() => field.calendar.show = false"
        >
          <Input 
            v-model="field.value"
            :warning="field.warning" 
            :error="field.error" 
            :tooltip="field.tooltip"
            :label="field.label" 
            :label-inside="field.labelInside ?? false"
            :disabled="field.disabled ?? false"
            :type="field.type"
            :require-show="field.requireShow ?? false"
            :mask="field?.mask?.mask ?? ''"
            :mask-token="field?.mask?.token ?? ''"
            :calendar="field?.calendar"
            @raw-value="(v) => { if (field?.mask?.raw !== undefined) field.mask.raw = v }"
            @mask-complete="(v) => { if (field?.mask?.complete !== undefined) field.mask.complete = v }"
            @show-calendar="(v) => { if (field?.calendar?.show !== undefined) field.calendar.show = v }"
          />

          <template #content>
            <DatePicker 
              ref="calendar"
              :mode="'date'"
              :update-on-input="true"
              :min-date="field.calendar.minDate ?? null"
              :max-date="field.calendar.maxDate ?? null"
              borderless 
              title-position="left" 
              v-model="field.calendar.value"
            />
          </template>
        </Popper>
      </InputWrapper>
    </template>
    <template v-else-if="['textarea'].includes(field.type)">
      <InputWrapper 
        :class="field.classWrap ?? ''"
        :info="getInfo(field)"
        :helper="field.helper" 
        :warning="field.warning"
        :error="field.error">
        <TextArea 
          v-model="field.value"
          :warning="field.warning" 
          :error="field.error" 
          :tooltip="field.tooltip"
          :label="field.label" 
          :label-inside="field.labelInside ?? false"
          :disabled="field.disabled ?? false"
          :type="field.type"
          :limit="field.limit"
          :require-show="field.requireShow ?? false"
          :min-height="field.minHeight ?? null"
        />
      </InputWrapper>
    </template>
    <template v-else-if="['texteditor'].includes(field.type)">
      <InputWrapper 
        :class="field.classWrap ?? ''"
        :info="getInfo(field)"
        :helper="field.helper" 
        :warning="field.warning"
        :label="field.label"
        :error="field.error">
        <TextEditor 
          v-model="field.value"
          :warning="field.warning" 
          :error="field.error" 
          :tooltip="field.tooltip"
          :label="field.label" 
          :label-inside="field.labelInside ?? false"
          :disabled="field.disabled ?? false"
          :type="field.type"
          :limit="field.limit"
          :require-show="field.requireShow ?? false"
          :min-height="field.minHeight ?? null"
        />
      </InputWrapper>
    </template>
    <template v-else-if="['checkbox'].includes(field.type)">
      <InputWrapper 
        :class="field.classWrap ?? ''"
        :info="field.info ?? ''"
        :helper="field.helper" 
        :label="field.label"
        :warning="field.warning" 
        :error="field.error">
        <div class="[ row gy-4 ]">
          <div 
            v-if="field?.values" 
            v-for="item in field.values"
            :class="item.width ?? '[ col-12 ]'" 
            :key="item.value">
            <Checkbox 
              v-model="field.value"
              :value="item.value"
              :label="item.label ?? ''"
              :disabled="item.disabled ?? false"
            />
          </div>
        </div>
      </InputWrapper>
    </template>
  </template>
</template>

<script>
import { formatDate } from '../../../utils/helper';
import Input from '@vue-main/global/components/ui_vue_input/components/Input.vue';
import Checkbox from '@vue-main/global/components/ui_vue_checkbox/components/Checkbox.vue';
import TextArea from '@vue-main/global/components/ui_vue_textarea/components/TextArea.vue';
import TextEditor from '@vue-main/global/components/ui_vue_texteditor/components/TextEditor.vue';
import InputWrapper from '@vue-main/global/components/ui_vue_input-wrap/components/InputWrap.vue';
import Popper from "vue3-popper";
import { DatePicker } from 'v-calendar';
import { format } from 'date-fns';

export default {
  components: {
    InputWrapper, 
    Input, 
    TextArea, 
    TextEditor,
    Checkbox, 
    DatePicker,
    Popper,
  },

  props: {
    field: Object
  },

  data () {
    return {
      state: this.field,
    }
  },

  created() {
    if (this.state?.type == 'calendar') {
      this.$watch('state.value', (newVal) => {
        if (newVal && newVal.length == 10) {
          this.$refs.calendar.move(formatDate(newVal))
          this.state.calendar.value = formatDate(newVal);
        }
      })

      this.$watch('state.calendar.value', (newVal) => {
        if (newVal !== null) this.state.value = format(newVal, 'dd.MM.yyyy');
        else this.state.value = null;
      });
    }
  },

  methods: {
    getInfo(field) {
      if (field.info) return field.info;
      if (field?.limit) return `${field.value.length}/${field.limit}`
      return '';
    },
  }
}
</script>

Ответы (1 шт):

Автор решения: Виктор Карев

Ну, я бы в первую очередь избавился от лишних темплейтов

<template>
  <InputWrapper v-if="field.active"
    :class="field.classWrap ?? ''"
    :info="field.info ?? ''"
    :helper="field.helper" 
    :warning="field.warning"
    :error="field.error">
    <Input v-if="['text', 'email', 'password'].includes(field.type)"
      v-model="field.value"
      :warning="field.warning" 
      :error="field.error" 
      :tooltip="field.tooltip"
      :label="field.label" 
      :label-inside="field.labelInside ?? false"
      :disabled="field.disabled ?? false"
      :type="field.type"
      :require-show="field.requireShow ?? false"
    />
    <Popper v-else-if="['calendar'].includes(field.type)"
      arrowPadding="16"
      arrow 
      :show="field?.calendar?.show" 
      v-click-outside="() => field.calendar.show = false"
    >
      <Input 
        v-model="field.value"
        :warning="field.warning" 
        :error="field.error" 
        :tooltip="field.tooltip"
        :label="field.label" 
        :label-inside="field.labelInside ?? false"
        :disabled="field.disabled ?? false"
        :type="field.type"
        :require-show="field.requireShow ?? false"
        :mask="field?.mask?.mask ?? ''"
        :mask-token="field?.mask?.token ?? ''"
        :calendar="field?.calendar"
      />
      <template #content>
        <DatePicker 
          ref="calendar"
          :mode="'date'"
          :update-on-input="true"
          :min-date="field.calendar.minDate ?? null"
          :max-date="field.calendar.maxDate ?? null"
          borderless 
          title-position="left" 
          v-model="field.calendar.value"
        />
      </template>
    </Popper>
  </InputWrapper>
</template>

Во-вторых, компонент Input, похоже, излишне универсальный. Потому что до него делается проверка на тип поля, так и в нём опять делается проверка на тип поля. После того, как тип поля определён, нужно использовать специализированные компоненты.

→ Ссылка