Как правильно создать компонент для генерации других компонентов 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, похоже, излишне универсальный. Потому что до него делается проверка на тип поля, так и в нём опять делается проверка на тип поля. После того, как тип поля определён, нужно использовать специализированные компоненты.