Как элегантно создавать пользовательские интерфейсы, управляемые данными, с помощью Vue

внешний интерфейс GitHub JavaScript Vue.js
Как элегантно создавать пользовательские интерфейсы, управляемые данными, с помощью Vue

переводить:Индигоиз Thunder Front End

Переведено сEvan SchultzстатьяDo it with Elegance: How to Create Data-Driven User Interfaces in Vue

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

Хотя мы обычно знаем, какие компоненты необходимы при создании большинства представлений, иногда мы не знаем, что они из себя представляют, до момента выполнения. Это означает, что нам нужно создавать представления на основе состояния приложения, пользовательских настроек или ответов на запросы API. Распространенным сценарием является создание динамических форм, в которых необходимые вопросы и компоненты настраиваются с помощью объектов JSON или где поля изменяются в зависимости от ответов пользователя.

Все современные фреймворки JavaScript имеют методы для обработки динамических компонентов. Этот пост покажет вам, как реализовать это в Vue.JS, который обеспечивает очень элегантное и простое решение для описанного выше сценария.

Как только вы увидите, как легко реализовать это с помощью Vue.JS, вы можете вдохновиться и начать думать о приложениях с динамическими компонентами, о которых вы никогда не думали раньше.

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

Основание

Vue имеет функцию под названием<component>Встроенные компоненты вы можете найти вРуководство по динамическим компонентам для VueJSдля получения полной информации.

Руководство гласит:

«Вы можете динамически переключаться между несколькими компонентами, используя одну и ту же точку монтирования и используя сохраненный элемент, динамически привязанный к его свойству».

Это означает, что переключение компонентов может быть таким простым, как:

<component :is="componentType">  

Давайте добавим еще немного, чтобы увидеть, что происходит. Мы создадим два компонента с именами DynamicOne и DynamicTwo. Один и два одинаковы, поэтому я не буду повторять код для обоих.

<template>  
  <div>Dynamic Component One</div>
</template>  
<script>  
export default {  
  name: 'DynamicOne',
}
</script>

Ниже приведен краткий пример возможности переключения между ними, мы настроили наш компонент в App.vue.

import DynamicOne from './components/DynamicOne.vue';
import DynamicTwo from './components/DynamicTwo.vue';

export default {
  name: 'app',
  components: {
    DynamicOne,
    DynamicTwo,
  },
  data() {
    return {
      showWhich: 'DynamicOne',
    };
  },
};

Уведомление:showWhichЗначение атрибута данных представляет собой строкуDynamicOne- это в компонентеcomponentsИмя свойства, созданного в объекте.

В нашем шаблоне мы настроим две кнопки для переключения этих двух динамических компонентов.

<button @click="showWhich = 'DynamicOne'">Show Component One</button>  
<button @click="showWhich = 'DynamicTwo'">Show Component Two</button>

<component :is="showWhich"></component>  

Нажатие этих двух кнопок переключит отображение DynamicOne и DynamicTwo.

Увидев это, вы можете подумать: «Ну и что? Это удобно — но я используюv-ifВот так просто».

когда ты понимаешь<component>Этот пример вступает в игру, когда он работает как любой другой компонент, и его можно использовать с такими компонентами, какv-forв сочетании с такими вещами, как перебор коллекций или объединениеisПривязать к входному свойству, свойству данных или вычисляемому свойству.

О реквизите и мероприятиях

Компоненты не изолированы, им нужен способ общения с окружающим миром. Во Vue этот подход реализован с помощью PROPS и событий.

Вы можете устанавливать реквизиты и привязывать события к динамическим компонентам так же, как и к любому другому компоненту, и Vue не будет выдавать ошибку неизвестного свойства, если загруженному компоненту это свойство не нужно.

Давайте изменим наш компонент, чтобы отобразить компонент приветствия. Компонент будет приниматьfirstNameа такжеlastName, другой приметfirstName,lastNameа такжеtitle.

Что касается мероприятия, мыDynamicOneдобавить кнопку вDynamicTwo, кнопка вызовет событие под названием «lowerCase».

Собрав их вместе, модифицированный динамический компонент выглядит так:

<component  
  :is="showWhich"
  :firstName="person.firstName"
  :lastName="person.lastName"
  :title="person.title"
  @upperCase="switchCase('upperCase')"
  @lowerCase="switchCase('lowerCase')">
</component>  

Не все свойства или события должны быть определены в динамическом компоненте, который мы переключаем.

Нужно ли знать реквизит заранее?

В этот момент у вас может возникнуть вопрос: «Если компоненты являются динамическими и не всем компонентам нужно знать все возможные реквизиты, нужно ли мне заранее знать реквизиты и объявлять их в шаблоне?»

К счастью, нет. Vue предоставляет ярлык, который вы можете использоватьv-bindПривязать все ключи объекта к свойствам компонента.

Это упрощает шаблон:

<component  
  :is="showWhich"
  v-bind="person"
  @upperCase="switchCase('upperCase')"
  @lowerCase="switchCase('lowerCase')">
</component>  

О формах

Теперь, когда у нас есть эти строительные блоки динамических компонентов, мы можем начать создавать наш конструктор форм поверх Vue.

Начнем с базовой схемы формы — объекта JSON, описывающего поля формы, метки, параметры и т. д. Во-первых, мы начнем со следующих типов форм ввода:

  • Текстовые и числовые поля ввода
  • список опций

Исходный шаблон таков:

schema: [
  {
    fieldType: 'SelectList',
    name: 'title',
    multi: false,
    label: 'Title',
    options: ['Ms', 'Mr', 'Mx', 'Dr', 'Madam', 'Lord'],
  },
  {
    fieldType: 'TextInput',
    placeholder: 'First Name',
    label: 'First Name',
    name: 'firstName',
  },
  {
    fieldType: 'TextInput',
    placeholder: 'Last Name',
    label: 'Last Name',
    name: 'lastName',
  },
  {
    fieldType: 'NumberInput',
    placeholder: 'Age',
    name: 'age',
    label: 'Age',
    minValue: 0,
  },
];

Кажется очень простым: можно настроить метки, заполнители и т. д., а в списке выбора также перечислены возможные варианты.options. В этом примере мы сохраним реализацию компонента как никогда просто.

TextInput.vue - template

<div>  
  <label>{{label}}</label>
  <input type="text"
    :name="name"
    placeholder="placeholder">
</div>  

TextInput.vue - script

export default {
  name: 'TextInput',
  props: ['placeholder', 'label', 'name'],
};

SelectList.vue - template

  <div>
    <label>{{label}}</label>
    <select :multiple="multi">
      <option v-for="option in options"
              :key="option">
        {{option}}
      </option>
    </select>
  </div>

SelectList.vue - script

export default {
  name: 'SelectList',
  props: ['multi', 'options', 'name', 'label'],
};

Чтобы сгенерировать форму на основе схемы, определенной выше, необходимо добавить следующее:

<component
  v-for="(field, index) in schema"  
  :key="index"
  :is="field.fieldType"
  v-bind="field">
</component>  

Эффект формы следующий:

привязка данных

Будет ли это работать, если форма сгенерирована, но не привязана к данным? Возможно нет. В настоящее время мы создаем форму, но нет возможности привязать к ней данные. Вашей первой реакцией может быть добавлениеvalueсвойство и использовать его в компонентеv-model,Следующим образом:

<input type="text"  
  :name="name"
  v-model="value"
  :placeholder="placeholder">

При таком подходе есть некоторые потенциальные ловушки, но нас больше всего беспокоит то, что Vue выдаст нам ошибку/предупреждение:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"

found in

---> <TextInput> at src/components/v4/TextInput.vue
       <FormsDemo> at src/components/DemoFour.vue
         <App> at src/App.vue
           <Root>

Хотя Vue предоставляет синтаксический сахар, облегчающий двустороннюю привязку состояния компонента, фреймворк по-прежнему отдает предпочтение одностороннему потоку данных. Мы пытаемся изменить родительские данные непосредственно внутри компонента, поэтому Vue предупредит нас.

Смотри внимательноv-modelЭто не так много магии, поэтому давайте следуемРуководство Vue по формированию входных компонентовчтобы разбить его, как описано в .

<input v-model="something">

так же, как ниже

<input  
  v-bind:value="something"
  v-on:input="something = $event.target.value">

Раскрыв магию, мы хотим добиться следующего:

  • Разрешить родительскому компоненту передавать значение дочернему компоненту
  • Сообщите родительскому компоненту, что значение было обновлено

Мы привязываемся кvalueи выпуск@inputсобытие, чтобы уведомить родительский компонент, что значение было изменено для достижения этой цели.

приходите посмотреть нашTextInputкомпоненты

 <div>
  <label>{{label}}</label>
  <input type="text"
    :name="name"
    :value="value"
    @input="$emit('input',$event.target.value)"
    :placeholder="placeholder">
  </div>

Поскольку родительский компонент отвечает за предоставление этого значения, он также отвечает за обработку состояния компонента, связанного с самим собой. Для этого мы можем использовать на компонентеv-model:

FormGenerator.vue - template

<component v-for="(field, index) in schema"
  :key="index"
  :is="field.fieldType"
  v-model="formData[field.name]"
  v-bind="field">
</component>  

Обратите внимание, как мы используемv-model ="formData[field.name]". Нам нужно установить объект для этого свойства данных:

export default {  
  data() {
  return {
    formData: {
      firstName: 'Evan'
    },
}

Мы можем оставить объект пустым, или, если у нас есть какие-то начальные значения полей, которые мы хотим установить, мы можем указать их здесь.

Теперь, когда мы закончили создание формы, мы обнаружили, что этот компонент берет на себя довольно много ответственности. Хотя это несложный код, было бы неплохо, если бы сам построитель форм был повторно используемым компонентом.

Создавайте многоразовые генераторы

Для этого построителя форм мы хотим передать ему схему в качестве реквизита и иметь возможность установить привязку данных между компонентами.

Шаблон с генератором такой:

GeneratorDemo.vue - template

<form-generator :schema="schema" v-model="formData">  
</form-generator>  

Это значительно упрощает родительский компонент. это заботит толькоFormGenerator, независимо от каждого доступного типа ввода, подключенных событий и т. д.

Далее создайте файл с именемFormGeneratorс компонент. Это в значительной степени копирование исходного кода, а затем внесение небольших, но важных изменений:

  • Будуv-modleизменить на:value, затем используйте@inputобрабатывать события
  • Добавить кvalueа такжеschemaк реквизиту
  • выполнитьupdateFormметод

Компонент FormGenerator выглядит следующим образом:

FormGenerator.vue - template

<component
  v-for="(field, index) in schema"
  :key="index"
  :is="field.fieldType"
  :value="formData[field.name]"
  @input="updateForm(field.name, $event)"
  v-bind="field">
</component>

FormGenerator.vue - template

import NumberInput from '@/components/v5/NumberInput';
import SelectList from '@/components/v5/SelectList';
import TextInput from '@/components/v5/TextInput';

export default {
  name: 'FormGenerator',
  components: {NumberInput, SelectList, TextInput},
  props: ['schema', 'value'],
  data() {
    return {
      formData: this.value || {},
    };
  },
  methods: {
    updateForm(fieldName, value) {
      this.$set(this.formData, fieldName, value);
      this.$emit('input', this.formData);
    },
  },
};

из-заformDataСвойства не знают о каждом полевом поле, в котором мы проходим, мы используемthis.$set, чтобы система реактивности Vue могла отслеживать любые изменения в нем и разрешатьFormGeneratorКомпонент отслеживает собственное внутреннее состояние.

Теперь у нас есть базовый многоразовый конструктор форм.

Используйте его внутри компонента:

GeneratorDemo.vue - template

<form-generator :schema="schema" v-model="formData">  
</form-generator>  

GeneratorDemo.vue - script

import FormGenerator from '@/components/v5/FormGenerator'

export default {  
  name: "GeneratorDemo",
  components: { FormGenerator },
  data() {
    return {
      formData: {
        firstName: 'Evan'
      },
      schema: [{ /* .... */ },
}

Теперь вы видели, как конструктор форм использует базовые динамические компоненты Vue для создания высокодинамичных пользовательских интерфейсов, управляемых данными. Я призываю вас учитьсяGitHubпример кода на или наCodeSanboxна практике. Если у вас есть какие-либо вопросы или вы хотите пообщаться, не стесняйтесь обращатьсяTwitter, Github, илиПочтасвяжитесь со мной.

Пролистайте и обратите внимание на внешний публичный аккаунт Xunlei.