Создание многоразовых элементов Ui с шаблоном прототипа в Vue.js

Понимание шаблона прототипа

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

По своей сути, шаблон опирается на прототип объекта, который служит в качестве чертежа. Любой новый объект, созданный из этого прототипа, наследует его свойства и методы. Новый объект может быть затем настроен без влияния на оригинальный прототип. Этот подход уменьшает избыточность, поощряет согласованность и упрощает обслуживание - когда прототип изменяется, все клоны на его основе автоматически обновляются (в живых ссылках на цепочку прототипов).

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

Паттерн прототипа в Vue.js

Компоненты Vue.js по сути являются объектами JavaScript, которые определяют шаблон, данные, методы, вычисленные свойства и крючки жизненного цикла. Из-за этой объектно-ориентированной природы Vue естественным образом поддерживает наследование на основе прототипа. Рамка предоставляет несколько механизмов для реализации шаблона прототипа:

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

Паттерн прототипа с API опций

В Vue 2 и Vue 3 Options API можно использовать опцию для создания детского компонента, который наследует от родительского компонента. Детский компонент может переопределять любые варианты (данные, методы, вычисленные и т.д.) при сохранении остальных. Это ближайшая реализация классического шаблона прототипа.

// BaseButton.js
export default {
 name: 'BaseButton',
 props: {
 label: { type: String, default: 'Click me' }
 },
 data() {
 return {
 isDisabled: false
 }
 },
 methods: {
 handleClick() {
 this.$emit('click')
 }
 },
 template: `<button :disabled="isDisabled" @click="handleClick" class="btn btn-primary">{{ label }}</button>`
}

// SecondaryButton.js
import BaseButton from './BaseButton'
export default {
 extends: BaseButton,
 data() {
 return {
 isDisabled: false
 }
 },
 template: `<button :disabled="isDisabled" @click="handleClick" class="btn btn-secondary">{{ label }}</button>`
}

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

Паттерн прототипа с составным API

API Vue 3 Composition поощряет использование композитных функций вместо наследования. Однако вы все равно можете реализовать концепцию прототипа, создав заводскую функцию, которая возвращает объект определения компонентов, а затем распространяет его на новый компонент. Такой подход более ясен и избегает глубокого слияния опций.

// useButtonPrototype.js
import { ref } from 'vue'
export function useButtonPrototype() {
 const isDisabled = ref(false)
 function handleClick(emit) {
 emit('click')
 }
 return { isDisabled, handleClick }
}

// BaseButton.vue
<script setup>
import { useButtonPrototype } from './useButtonPrototype'
const emit = defineEmits(['click'])
const { isDisabled, handleClick } = useButtonPrototype()
</template>
<template>
 <button :disabled="isDisabled" @click="handleClick(emit)" class="btn btn-primary">
 <slot />
 </button>
</template>

// SecondaryButton.vue
<script setup>
import { useButtonPrototype } from './useButtonPrototype'
const emit = defineEmits(['click'])
const { isDisabled, handleClick } = useButtonPrototype()
// Add secondary logic here
</template>
<template>
 <button :disabled="isDisabled" @click="handleClick(emit)" class="btn btn-secondary">
 <slot />
 </button>
</template>

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

Создание многоразового базового компонента

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

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

<template>
 <transition name="fade">
 <div v-if="visible" class="dialog-overlay" @click.self="close">
 <div class="dialog" :class="sizeClass">
 <button class="dialog-close" @click="close">&times;</button>
 <div class="dialog-body">
 <slot />
 </div>
 </div>
 </div>
 </transition>
</template>

<script>
export default {
 props: {
 visible: Boolean,
 size: { type: String, default: 'medium' }
 },
 computed: {
 sizeClass() { return `dialog--${this.size}` }
 },
 methods: {
 close() { this.$emit('update:visible', false) }
 }
}
</script>

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

Клонирование и расширение компонентов

Vue предоставляет несколько способов клонирования и расширения компонентов. Опция является наиболее простой для небольших модификаций. Однако будьте осторожны с глубоким наследованием: Vue объединяет опции в определенном порядке (компонентные собственные опции > расширяет > смешивает). Переопределяющие свойства данных или методы могут привести к неожиданному поведению, если вы не будете осторожны.

для простых вариантов

// ConfirmDialog.js
import BaseDialog from './BaseDialog.vue'
export default {
 extends: BaseDialog,
 props: {
 confirmText: { type: String, default: 'OK' },
 cancelText: { type: String, default: 'Cancel' }
 },
 template: `
 <BaseDialog v-bind="$props" v-on="$listeners">
 <template #default>
 <slot />
 </template>
 <template #footer>
 <button @click="close">{{ cancelText }}</button>
 <button @click="$emit('confirm')">{{ confirmText }}</button>
 </template>
 </BaseDialog>
 `,
 components: { BaseDialog }
}

Обратите внимание, что использует как прототип (через ), так и как детский компонент в шаблоне. Этот шаблон позволяет добавлять нижний колонтитул с кнопками при повторном использовании наложения, перехода и тесной логики базового диалога.

Программное клонирование с помощью (Vue 2) или (Vue 3)

Для динамических сценариев можно клонировать определение компонента во время выполнения. В Vue 3:

import { defineComponent, h } from 'vue'
import BaseButton from './BaseButton.vue'

function createButtonVariant(customProps) {
 return defineComponent({
 extends: BaseButton,
 setup(props, { slots }) {
 // Override or extend behavior
 return () => h(BaseButton, { ...customProps, ...props }, slots)
 }
 })
}

Этот метод полезен при создании реестра компонентов, который генерирует варианты на основе объектов конфигурации из CMS или API.

Использование слотов для кастомизации

Слоты являются основной особенностью Vue, которая красиво выравнивается с шаблоном прототипа. Определяя слоты в базовом компоненте, вы позволяете клонам заменять или дополнять части пользовательского интерфейса без переписывания всего компонента. Названные слоты позволяют несколько точек настройки.

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

Смеси, расширения и композиты: что использовать?

Паттерн прототипа может быть реализован с помощью миксов, расширений или композитных файлов. Вот краткое сравнение:

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

Пример из реального мира: создание многоразовой системы уведомлений

Представьте, что вам нужно отображать уведомления в вашем приложении: успех, ошибку, предупреждение и информацию. Каждый вариант имеет одинаковую компоновку (иконка, сообщение, кнопка закрытия), но отличается цветом и значком. Используя шаблон прототипа, вы можете создать компонент , а затем клонировать его для каждого типа.

  1. Определите с реквизитом для , и слотами для значка. Включите логику для автоустранения и анимации.
  2. Создайте композитный , который управляет очерёдностью уведомлений (аналогично пулу прототипов).
  3. Создавайте отдельные компоненты уведомлений, такие как , которые расширяют или составляют с зеленой цветовой схемой и значком галочки.
  4. Регистрируйте эти компоненты глобально или используйте динамический компонент на основе типа.

Преимущество: все уведомления имеют одинаковые атрибуты отклонения логики, перехода и доступности.Изменение базового компонента (например, добавление swipe-to-dismiss) автоматически обновляет каждый вариант.

Преимущества и ограничения шаблона прототипа

Преимущества:

Ограничения:

  • Над клонированием: В JavaScript объекты клонирования могут быть неглубокими или глубокими. Неосторожное клонирование может привести к непреднамеренной мутации общих ссылок.
  • Тяжелая связь в наследстве: Использование может сделать варианты хрупкими, если базовый компонент изменяет внутреннюю структуру.
  • Сложность в глубоких иерархиях: Многоуровневое наследование может стать трудной задачей.
  • Потенциал для дублирования в шаблонах: Если клонам нужно переопределить большие части шаблона, вы можете в конечном итоге скопировать весь шаблон, а не наследовать его.

Сравнение с заводским шаблоном

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

Лучшие практики использования шаблона прототипа в Vue

  • Прототипы дизайна с учетом расширения : Используйте реквизиты, слоты и события свободно. Избегайте наименований классов жесткого кодирования или встроенных стилей, которые нельзя переопределить.
  • Предпочтительно композитные над миксами в Vue 3. Они обеспечивают явные зависимости и избегают столкновений имен.
  • Ограничить глубину наследования: Не более двух-трех уровней. Глубокие цепи становится трудно отлаживать.
  • Документация прототипа интерфейса: Ясно состояние, какие реквизиты, слоты и методы предназначены для переопределения.Это помогает другим разработчикам создавать последовательные клоны.
  • Использовать TypeScript: Определение интерфейсов для опций компонентов и реквизитов делает клонирование более безопасным и предсказуемым.
  • Избегайте экземпляров компонентов клонирования во время выполнения , если вы можете использовать определения статических компонентов.
  • Проверяйте прототип самостоятельно.Тогда проверяйте каждый вариант только на его настройки.

Продвинутый: клонирование программных компонентов с помощью

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

import { defineComponent, h } from 'vue'

function createClonedComponent(base, overrides = {}) {
 return defineComponent({
 name: overrides.name || `Cloned${base.name}`,
 extends: base,
 setup(props, { slots, emit }) {
 // Apply overrides logic
 const merged = { ...base.setup?.(props, { slots, emit }), ...overrides.setup?.(props, { slots, emit }) }
 return merged
 }
 })
}

const SuccessNotification = createClonedComponent(BaseNotification, {
 name: 'SuccessNotification',
 setup() {
 // Add success-specific logic
 const icon = 'check-circle'
 return { icon }
 }
})

Такой подход позволяет генерировать весь набор пользовательского интерфейса из набора прототипов и конфигурационных файлов.

Заключение

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

Для дальнейшего чтения, изучите документацию по API композиции и Patterns.dev: Prototype Pattern. Кроме того, проверьте обсуждения по Vue.extend vs mixins на Stack Overflow для понимания сообщества.