Перевод: Markus Oberlehner — Implementing the Builder Pattern in Vue.js Part 2: Forms
В первой части статьи о реализации шаблона Builder во Vue.js мы рассмотрели, как можно использовать эту технику для быстрого создания множества разных вариантов одного и того же компонента.
Еще один пример когда очень часто приходится создавать похожие компоненты — это формы в приложении CRUD с множеством различных типов содержимого. В этой части мы рассмотрим, как можно использовать шаблон Builder, чтобы упростить создание множества различных компонентов формы для каждого типа содержимого типичного приложения CRUD.
Как и в примере описанным в первой части, это часть также во многом вдохновлена выступлением Якоба Шаца. В своем выступлении он также показывает возможное решение для реализации форм с помощью шаблона Builder. Я настоятельно рекомендую вам посмотреть его видео, если вы еще этого не сделали.
Чтобы оставаться верными промышленной схеме именования, мы начинаем с создания нового компонента FormFactory. Этот компонент отвечает за создание формы из массива объектов полей.
<template> <form class="form-factory" @submit.prevent="submit" > <div v-if="success" class="form-factory-success" > Success! </div> <template v-else> <FormGroup v-for="field in fieldsWithDefaults" :key="field.name" > <FormLabel :for="`${_uid}-${field.name}`"> {{ field.label }} <template v-if="field.validation.required">*</template> </FormLabel> <Component v-model="data[field.name]" :is="field.component" v-bind="{ ...field.options.props, ...field.options.attrs, }" :id="`${_uid}-${field.name}`" @input="$v.data[field.name].$touch()" /> <FormInlineMessage v-if="$v.data[field.name].$error" > Please fill in this field correctly. </FormInlineMessage> </FormGroup> <button>Submit</button> </template> </form> </template> <script> // src/components/FormFactory.vue import { validationMixin } from 'vuelidate'; import FormGroup from './FormGroup.vue'; import FormInlineMessage from './FormInlineMessage.vue'; import FormLabel from './FormLabel.vue'; const defaultField = { component: null, label: '', name: '', options: {}, validation: {}, }; export default { name: 'FormFactory', // We use the vuelidate validation // Mixin for basic form validation. mixins: [validationMixin], // Injecting dependencies makes it // possible or reuse this component // for all kinds of content types. inject: ['fetch', 'post'], components: { FormGroup, FormInlineMessage, FormLabel }, props: { fields: { default: () => [], type: Array, }, id: { default: null, type: [Number, String], }, }, data() { return { data: {}, success: false, }; }, computed: { // Apply default field configuration // to make sure all properties we rely // on in the template do exist. fieldsWithDefaults() { return this.fields.map(x => ({ ...defaultField, ...x })); }, }, async created() { // If there is an ID we initially // load the data and switch into // edit mode. if (this.id) { this.data = await this.fetch(this.id); } }, methods: { async submit() { this.$v.$touch(); if (this.$v.$error) return; const { success } = await this.post(this.data); this.success = success; }, }, // The vuelidate validation configuration is // automatically generated for us. validations() { const data = this.fieldsWithDefaults .filter(x => x.validation) .reduce((prev, field) => ({ ...prev, [field.name]: field.validation, }), {}); return { data }; }, }; </script> <style> .form-factory > :not(:first-child) { margin-top: 1em; } .form-factory-success { color: green; } </style>
В приведенном выше фрагменте кода вы можете видеть, что этот компонент инкапсулирует множество разных параметров. Эго реализация может показаться не идеальна, но в будущем она значительно упростит создание новых компонентов формы, которые будут полностью функциональными с самого начала, без необходимости беспокоиться о макете и логике проверки или отправки формы.
Если вы хотите поближе взглянуть на код примера, который вы видите выше, вы можете увидеть полную демонстрацию в этом CodeSandbox.
В следующем фрагменте кода вы можете увидеть, как мы можем использовать нашу только что созданную FormFactory для создания нового компонента UserForm, который наши пользователи могут использовать для изменения своих настроек.
<template> <UserProvider> <FormFactory :fields="fields" :id="id"/> </UserProvider> </template> <script> // src/components/UserForm.vue import { required } from 'vuelidate/lib/validators'; import FormFactory from './FormFactory.vue'; import FormInput from './FormInput.vue'; import FormTextarea from './FormTextarea.vue'; import UserProvider from './UserProvider.vue'; export default { name: 'UserForm', components: { FormFactory, FormInput, FormTextarea, UserProvider, }, props: { // Passing an ID as a property makes // the form load an existing user and // switches the form into editing mode. id: { default: null, type: [Number, String], }, }, created() { this.fields = [ { component: FormInput, label: 'Name', name: 'name', options: { attrs: { placeholder: 'Your name', }, }, validation: { required, }, }, { component: FormTextarea, label: 'Description', name: 'description', options: { attrs: { placeholder: 'About you', }, }, }, ]; }, }; </script>
Хотя это уже кажется довольно простым, мы можем упростить инициализацию новых форм с помощью шаблона Builder.
В дополнение к тому, что вам не нужно создавать новый компонент или повторять один и тот же подробный код шаблона для каждой новой формы, шаблон Builder также позволяет очень простое динамическое создание новых компонентов, например на основе пользовательского ввода. Давайте посмотрим на возможную реализацию этого шаблона.
// src/builders/FormBuilder.js import FormFactory from '../components/FormFactory.vue'; export default class FormBuilder { constructor() { this.props = { fields: [] }; } withProvider(provider) { this.provider = provider; return this; } addField(field) { this.props.fields.push(field); return this; } build() { const Provider = this.provider; const props = this.props; return { props: { id: { default: null, type: [Number, String], }, }, render(h) { return h(Provider, [ h(FormFactory, { props: { id: this.id, ...props } }), ]); }, }; } }
В следующем блоке кода вы можете увидеть, как мы можем использовать FormBuilder внутри нашего корневого компонента App.vue для создания новой UserForm «на лету».
<template> <div id="app"> <h2>Create User Form</h2> <UserForm/> <h2>Edit User Form</h2> <UserForm :id="1"/> </div> </template> <script> // src/App.vue import { required } from 'vuelidate/lib/validators'; import FormBuilder from './builders/FormBuilder'; import UserProvider from './components/UserProvider.vue'; import FormInput from './components/FormInput.vue'; import FormTextarea from './components/FormTextarea.vue'; export default { name: 'App', components: { UserForm: new FormBuilder() .withProvider(UserProvider) .addField({ component: FormInput, label: 'Name', name: 'name', options: { attrs: { placeholder: 'Your name', }, }, validation: { required, }, }) .addField({ component: FormTextarea, label: 'Description', name: 'description', options: { attrs: { placeholder: 'About you', }, }, }) .build(), }, }; </script>
Хотя инициализация новых компонентов формы очень проста с классом FormBuilder, она также может стать очень утомительной, если мы хотим повторно использовать определенный компонент формы в нескольких местах. Что бы упростить этот момент используем шаблон Director.
import { required } from 'vuelidate/lib/validators'; import UserProvider from '../components/UserProvider.vue'; import FormInput from '../components/FormInput.vue'; import FormTextarea from '../components/FormTextarea.vue'; export default class FormDirector { constructor(builder) { this.builder = builder; } makeUserForm() { return this.builder .withProvider(UserProvider) .addField({ component: FormInput, label: 'Name', name: 'name', options: { attrs: { placeholder: 'Your name', }, }, validation: { required, }, }) .addField({ component: FormTextarea, label: 'Description', name: 'description', options: { attrs: { placeholder: 'About you', }, }, }) .build(); } }
В следующем примере вы можете увидеть, как мы можем использовать класс FormDirector сверху, чтобы быстро получить определенный компонент формы.
<template> <div id="app"> <h2>Create User Form</h2> <UserForm/> <h2>Edit User Form</h2> <UserForm :id="1"/> </div> </template> <script> // src/App.vue import FormBuilder from "./builders/FormBuilder"; import FormDirector from "./builders/FormDirector"; export default { name: 'App', components: { UserForm: new FormDirector( new FormBuilder(), ).makeUserForm(), }, }; </script>
Благодаря шаблону Director нам не нужно повторяться, чтобы создать один и тот же компонент формы в нескольких местах нашего приложения.
Хотя примеры, приведенные в этой статье, очень хорошо демонстрируют преимущества, не все идеально при таком подходе. Шаблон Builder обычно работает очень хорошо, если у нас много очень похожих компонентов. Как только вам понадобится форма с немного другим макетом или поведением, использование шаблона будет сложным. Но с другой стороны, поскольку мы по-прежнему используем обычные компоненты в качестве базовых строительных блоков для наших форм, мы можем решить не использовать шаблон Builder в таких случаях, а построить обычный компонент из этих компонентов формы.
Но в целом, я определенно вижу, что для этого паттерна есть своя ниша. Хотя он и не панацея для каждой проблемы, но в некоторых случаях может быть очень элегантным решением.
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…