Дорога к продвинутым фронтальным формам: форма конфигуративности через Vue.js

задняя часть внешний интерфейс JavaScript Vue.js

Разработка форм — одна из самых распространенных потребностей в веб-разработке, и сложность самих форм постоянно растет. Как мы можем использовать технические средства, чтобы лучше реализовать структуру формы и организовать бизнес-код? В этой статье представлен некоторый опыт создания настраиваемых форм с помощью Vue.js.

задний план

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

Традиционные веб-приложения используют метод прямого вывода форм на стороне сервера для вывода различного содержимого форм для разных логик страниц. Некоторые относительно полные фреймворки будут обеспечивать функцию вывода форм на стороне сервера посредством некоторой простой настройки. Например, PHP-фреймворк Laravel предоставляетForm::textarea('content', null, ['class' => 'form-control'])Такой способ позволяет отображать элемент управления формы в слое шаблона представления. Однако в сегодняшней все более сложной логике взаимодействия многие требования, такие как проверка полей в реальном времени и связь между элементами управления, очень сложно реализовать в этом режиме, а простой рендеринг на стороне сервера далек от удовлетворения потребностей бизнеса в разработке.

WPF от Microsoft был первым, кто продемонстрировал нам шаблон приложений MVVM, а Knockout привнес его в мир внешнего интерфейса. До сих пор фреймворки уровня представления, представленные React и Vue, хорошо внедряли этот шаблон в производство. В этой статье мы расскажем об оптимизации наших возможностей и опыта разработки форм с помощью платформы Vue.js.

Цель

Если оставить в стороне технологические исследования, чего мы пытаемся достичь с помощью форм?

Только представьте, есть такие требования:

  1. В простейшей форме требуются три поля: содержание, местоположение и контактная информация.
  2. Поле содержания должно быть заполнено не менее 8 символов и не может содержать несколько простых запрещенных фраз.
  3. Поле местоположения представляет собой древовидный элемент управления выбором, который должен предоставлять пользователям возможность выбора от уровня провинции до уровня округа и округа.
  4. Контактная информация обязательна, и в этом поле должен быть номер мобильного телефона.
  5. Если номер мобильного телефона отображается в поле содержимого, а пользователь не вводит номер, номер должен быть автоматически добавлен в контактную информацию.

Ведь даже форма с таким простым содержанием будет иметь такой спрос. Есть некоторые функции, такие как: требуется, проверка формата, мы можем передать HTML5requiredилиpatternТакие поля реализуют нативные ограничения, тогда как более сложные функции должны быть переданы в JavaScript. Помимо этой части, в чистой структуре страницы нам нужно что-то вроде этого:

<form class="form">
  <div class="form-line">
    <div class="form-control">
      <textarea name="content"></textarea>
    </div>
  </div>
  <div class="form-line">
    <div class="form-control">
      <input type="hidden" name="address">
      <!-- 具体的控件实现 -->
    </div>
  </div>
  <div class="form-line">
    <div class="form-control">
      <input type="text" name="contact">
    </div>
  </div>
  <input type="hidden" name="_token" value="1wev5wreb8hi1mn=">
  <button type="submit">提交</button>
</form>

И мы ожидаем иметь такую ​​конфигурацию для непосредственной настройки вышеуказанной структуры страницы и некоторой ее логики:

[
  {
    "type": "textarea",
    "name": "content",
    "validators": [
      "minlength": 8
    ]
  },
  {
    "type": "tree",
    "name": "address",
    "datasrc": "areaTree",
    "level": 3
  },
  {
    "type": "text",
    "name": "contact",
    "required": true,
    "validators": [
      "regexp": "<mobile>",
    ]
  }
]

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

выполнить

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

Базовая логика реализации показана на следующем рисунке:

Весь процесс можно разделить на две части: внутренняя передача данных (пурпурный) и внешнее расширение (синий).Далее будут подробно представлены основные процессы каждой части.

серверная передача данных

Окружающая среда Runtime Vue.js ориентирована, чтобы хорошо поддерживаться большинством мобильных браузеров [2]. Таким образом, мы можем записать следующий код непосредственно в HTML или соответствующему файлу шаблона:

<div id="my-form">
  <my-form :schema="schema" :context="context"></my-form>
  <script type="text/json" ref="schema">{!! json_encode($schema) !!}</script>
  <script type="text/json" ref="context">{!! json_encode($context) !!}</script>
</div>

(Примечание: здесь используется язык Blade [3])

#my-formЭтот элемент объявлен как корневой контейнер, который мы передаем Vue, и<my-form>Это элемент управления, который мы создали для формы. Здесь стоит отметить, что мы передаемrefизscriptтеги, чтобы мы могли передавать данные компонентам Vue из бэкэнда.

Здесь я использую два объекта данных из бэкенда.schemaпохож на содержимое конфигурации, о котором я упоминал в предыдущем разделе, оно будет передано соответствующему элементу управления формой через корневой контейнер Vue; иcontextОн используется для обработки других данных, которые должны быть прочитаны серверной частью. Например, некоторые коды могутроль пользователяДля обработки мы также можем передать эту часть информации в JS для удобства управления.

В файле JS мы можем использовать следующие методы для обработки вышеуказанных данных:

new Vue({
    // ...
    mounted() {
        this.schema = JSON.parse(this.$refs.schema.innerText)
        this.context = JSON.parse(this.$refs.context.innerText)
    }
})

Таким образом, мы можем достичьform.vueдля реализации нашей конструкции формы.

Примечания

  1. vuejs.org/v2/examples
  2. руб. newser.com/#search=ECM…
  3. Вытащить Ravel.com/docs/5.4/ было бы…

Создание элементов управления формой

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

<template>
  <form :class="form" method="post">
    <my-line v-for="(item, index) in schema" :schema="item"></my-line>
  </form>
</template>

my-lineЭтот элемент, здесь мы используем для создания единого шаблона формы, например, все элементы управления будут<div class="form-line"></div>Такой пакет контейнеров, то мы можем использовать эту часть содержимого какmy-lineШаблон объявления элемента. Используя этот метод, мы можем создать тот же элемент Label, сообщение об ошибке и т. д.

существуетmy-lineВ компоненте мы можем объявить фактический элемент управления формы следующим образом:

<div class="form-ctrl">
  <my-input :schema="schema" v-if="schema.type === 'input'"></my-input>
  <my-textarea :schema="schema" v-else-if="schema.type === 'textarea'"></my-textarea>
</div>

Такой подход кажется простым и понятным, но он делаетmy-lineКомпоненты становятся невероятно сложными. Чтобы решить эту проблему, мы можем ввести фиктивный компонентmy-control, само по себе по разнымschema.typeРазрешить разные элементы формы.

Используется в Vue.jsфункциональные компонентыВы можете объявить компонент, который не отображает себя, но может вызывать дочерние компоненты. Нам просто нужно объявить это так:

<div class="form-ctrl">
  <my-control :schema="schema"></my-control>
</div>
// my-control.js
function getControl(context) {
  const type = context.props.schema.type
  // 在这里分发组件
}
export default {
  functional: true,
  props: {
    schema: Object
  },
  render(h, context) {
    return h(getControl(context), context)
  }
}

Таким образом, сложность управления может быть уменьшена сmy-lineИзвлечение этого компонента более удобно для независимого обслуживания каждого компонента.

контроль наследования

Как упоминалось выше, мы уже можем размещать различные элементы управления, такие какmy-input,my-textareaРеализовать самостоятельно. Однако в этих компонентах может быть некоторая общая логика. Например, отображаемое имя поля формы, соответствующего элементу управления, нам действительно нужен этот атрибут:

export default {
  // ...
  computed: {
    displayName() {
      // 如果有独立配置就使用配置的名称,而默认使用表单项的 name 属性作为名称
      return this.schema.displayName || this.schema.name
    }
  }
}

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

// contract.js
export default {
  // 一些公用的方法
}
// input.vue
import Contract from './contract'
export default {
  mixins: [Contract]
  // ...
}

И, благодаря механизму миксинов Vue, мы можемcontract.jsУнифицированная функция жизненного цикла объявлена ​​в элементе управления, а в компоненте, соответствующем элементу управления, повторное объявление функции жизненного цикла не отменит унифицированную обработку, но будет выполняться после унифицированной функции. Это гарантирует, что мы можем безопасно объявить независимые жизненные циклы без повторного добавления единой логики.

внешний элемент

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

<!-- template -->
<div id="my-form">
  <my-form :schema="schema" :context="context"></my-form>
  <div class="action" slot="action">
    <button class="form-submit" type="submit">{{ $btnText }}</button>
  </div>
</div>
<!-- my-form -->
<template>
  <form :class="form" method="post">
    <my-line v-for="(item, index) in schema" :schema="item"></my-line>
    <slot name="action"></slot>
  </form>
</template>

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

расширять

После завершения базовых компонентов у нас остаются некоторые базовые функции взаимодействия, а также функции, которые может учитывать бизнес-логика. Например, упомянутый вышеНеобходимыйЖдать.这时候,我们需要从 JavaScript 角度对我们的表单进行扩展。

Чтобы предотвратить распространение бизнес-логики на логику управления, нам нужно предоставить механизм, чтобы бизнес-логика могла выполняться в соответствующий момент. Например,НеобходимыйИстинное значение на самом делеКогда управляющие данные изменяются, наблюдайте, не пусты ли они. Отключить кнопку отправки, если требуемые данные пусты. Очевидно,При изменении управляющих данныхЭто процесс жизненного цикла (обновление или пользовательское событие @change), поэтому мы можем реализовать фреймворк для обработки бизнес-логики через механизм доставки событий.

Ядром формы являются Form (элемент формы) и Control (элемент управления), поэтому нам нужно проксировать события соответствующего основного элемента управления через отдельный Event Emitter.

const storage = {
  proxy: {
    form: null,
    control: {}
  }
}
class Core {
  constructor(target) {
    this.target = target
  }
  static control(name) {
    return storage.proxy.control[name] ||
      (storage.proxy.control[name] = new CoreProxy(`control.${name}`))
  }
  static form() {
    return storage.proxy.form ||
      (storage.proxy.form = new CoreProxy('form'))
  }
  mount(target) {
    // ...
  }
  on(events, handler) {
    // ...
  }
  emit(events, ...args) {
    // ...
  }
}

Таким образом, мы можем пройтиCore.form()или что-то вродеCore.control('content')способ получить излучатель, который постоянно присутствует на текущей странице. Затем нам просто нужно проксировать события жизненного цикла в соответствующем файле Vue:

import Core from './core.js'
export default {
  // ...
  beforeUpdate() {
    // 避免初始化之前产生事件
    if (!this.schema.length) return
    Core.form().mount(this).emit('create', this)
  },
}

Чтобы избежать глобального импортаCoreProxy, вы можете выставить этот класс вVue.prototypeначальство. С помощью плагина Vue можно добиться следующих эффектов:

// contract.js
export default {
  // ...
  updated() {
    this.$core.control(this.schema.name).emit('update', this)
    // propagation
    this.$core.form().emit('update', this)
  }
}

Таким образом, мы можем передать соответствующий объект Vue в Core для прокси, но в то же время не выставлять его напрямую внешнему миру. Например, наш код может выглядеть так:

// 这个文件用来实现“必填”功能
Core.form().on('update', function(control) {
  if (!control.schema.required) return
  if (control.model) {
    // error对应的事件由其他文件来处理
    Core.form().emit('resolve-error', control, 'required')
  } else {
    Core.form().emit('reject-error', control, 'required', '此项必填')
  }
})

Точно так же мы можем передавать события в разные компоненты. Например, нам нужноПроверьте поле «Контакт», когда «Тип» выбран как «Номер мобильного телефона».:

Core.control('contact-type').on('change', function(control) {
  // 这里我们不能直接读取到“联系方式”,应该通过其他的方式来处理
  const proxy = Core.control('contact')
  const contact = proxy.read()
  // ...
})

Потому что внутри Core мы можем получить соответствующий объект Vue, поэтому мы можем предоставить что-то вродеreadТакие методы только для чтения предназначены для внешних вызовов; для модификации данных, например, внешние данные также могут нуждаться в модификации для других элементов управления, мы также можем предоставить некоторые встроенные события, такие какCore.control('contact').emit('write', newValue)Чтобы снаружи иметь возможность изменять эти данные, в то же время можно получить единый контроль.

Суммировать

Начав с конца, давайте, наконец, поговорим о том, почему наши формы лучше выражены в таком фреймворке, как Vue:

  1. Двусторонний механизм крепления. Двусторонняя привязка означает, что нам не нужно заботиться ни о перерисовке представления из-за изменения данных, ни о том, как пользовательские операции синхронизируются с модификацией данных JS, что значительно упрощает нашу обработку данных; в то же время, Vue обеспечивает синхронизацию данных, множество опций, таких как.lazy,.trimи другие модификаторы, позволяющие сосредоточиться на обработке самой логики
  2. компонентный. Сама форма является очень подходящей сценой для компонентизации, поскольку каждый элемент управления формы показывает общие черты и различия, а сама форма состоит из различных элементов управления. Можно сказать, что абстрагирование элементов управления в компоненты является неизбежным и лучшим решением для обработки элементов управления.
  3. Описание шаблона. Vue использует шаблоны для описания того, как отрисовывать компонент.Поскольку элементы управления формы в HTML сами по себе представляют собой большую логическую инкапсуляцию, более прямо и естественно использовать шаблоны для описания элемента управления формы, чем функции рендеринга. Используя официальный язык Vue, чтобы объяснить, по сравнению с логическим компонентом, сам элемент управления формой на самом деле является частичным представлением (презентационным), поэтому шаблон, очевидно, будет иметь лучшую производительность.

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


автор:Сунь Иран
Введение: Front-end инженер, инженер веб-разработки, занимающийся проектированием и разработкой бизнес-архитектуры на основе различных фреймворков и языков.

Эта статья является лишь личной точкой зрения автора и не отражает позицию People's Network.


Эта статья была впервые опубликована в общедоступной учетной записи WeChat «Технической группы People's Network», отсканируйте код и немедленно подпишитесь:

Категории