Создание многоразовых элементов Ui с шаблоном прототипа в Vue.js
Понимание шаблона прототипа
Паттерн прототипа — это шаблон креационного дизайна, который сосредотачивается вокруг концепции клонирования существующих объектов, а не создания новых с нуля. Он особенно ценен, когда стоимость создания нового экземпляра класса дороже, чем копирование существующего, или когда система должна быть независимой от того, как создаются ее объекты. В JavaScript объекты могут быть клонированы легко с помощью или оператора спреда, что делает шаблон прототипа естественным для фреймворков, таких как Vue.js.
По своей сути, шаблон опирается на прототип объекта, который служит в качестве чертежа. Любой новый объект, созданный из этого прототипа, наследует его свойства и методы. Новый объект может быть затем настроен без влияния на оригинальный прототип. Этот подход уменьшает избыточность, поощряет согласованность и упрощает обслуживание - когда прототип изменяется, все клоны на его основе автоматически обновляются (в живых ссылках на цепочку прототипов).
В контексте разработки пользовательского интерфейса шаблон прототипа позволяет командам создавать библиотеку многоразовых элементов, которые имеют общее поведение, стиль и логику. Вместо дублирования кода для аналогичных компонентов разработчики клонируют базовый компонент и адаптируют его к конкретным потребностям. Это приводит к более быстрым циклам разработки и меньшему количеству ошибок.
Паттерн прототипа в Vue.js
Компоненты Vue.js по сути являются объектами JavaScript, которые определяют шаблон, данные, методы, вычисленные свойства и крючки жизненного цикла. Из-за этой объектно-ориентированной природы Vue естественным образом поддерживает наследование на основе прототипа. Рамка предоставляет несколько механизмов для реализации шаблона прототипа:
- Расширение компонента с помощью : : опция Vue позволяет компоненту наследовать от другого компонента, сливающихся опций.
- Миксины: Смеси — это многоразовый блок опций компонентов, который можно объединить в любой компонент, эффективно клонируя поведение на нескольких прототипах.
- Вместимые (Composition API): С Vue 3 композитные функции позволяют повторно использовать логику, не полагаясь на наследование объектов, предлагая более гибкую форму клонирования прототипа.
- Динамическое клонирование компонентов: Используя или вместе с или операторами спреда, разработчики могут создавать новые определения компонентов на основе существующих во время выполнения.
Паттерн прототипа хорошо согласуется с реактивной системой 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 является разработка твердого базового компонента. Этот компонент должен инкапсулировать общую структуру и поведение, которые будут разделять все варианты. Для элемента пользовательского интерфейса, такого как кнопка, который включает в себя:
- (11) или [12], с помощью которых можно получить доступ к данным.
- В этом случае, как правило, речь идет о , , и т.д.
- Слоты для проекции контента.
- Излучатели событий для взаимодействий.
- Общий стиль через классы CSS или систему проектирования.
Хорошо продуманный базовый компонент действует как прототип. Он должен быть достаточно гибким, чтобы обрабатывать большинство вариантов использования, не будучи чрезмерно общим. Например, компонент может включать в себя фон, кнопку закрытия, переход и слот для контента, позволяя вариантам добавлять пользовательские заголовки, нижние колонтитулы или анимацию.
<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">×</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, которая красиво выравнивается с шаблоном прототипа. Определяя слоты в базовом компоненте, вы позволяете клонам заменять или дополнять части пользовательского интерфейса без переписывания всего компонента. Названные слоты позволяют несколько точек настройки.
Например, компонент может иметь слоты для , и . Клонированный может заполнять заголовок заголовком, слот по умолчанию с изображением и описанием и нижний колонтитул с кнопкой. Базовый прототип остается неизменным, но клон может производить совершенно разные карты.
Смеси, расширения и композиты: что использовать?
Паттерн прототипа может быть реализован с помощью миксов, расширений или композитных файлов. Вот краткое сравнение:
- Миксины: Полезно, когда вы хотите разделить поведение между несколькими несвязанными компонентами. Однако, миксины могут привести к столкновениям и неясным источникам свойств. Они являются формой клонирования прототипа, но часто не рекомендуется в пользу композитных в Vue 3.
- Расширяет: Лучше всего для создания прямой иерархии родительско-детской составляющей. Это интуитивно понятно, когда у вас есть четкий базовый прототип и вариант, который наследует все варианты. Недостатком является то, что он создает плотную связь.
- Вместимые : Наиболее гибкий подход. Они позволяют извлекать логику в многоразовые функции, которые могут быть составлены в любой компонент.Вместимые не создают цепочку прототипов; вместо этого они клонируют поведение через вызов функции.Это часто предпочтительнее для современных приложений Vue.
Для большинства библиотек элементов пользовательского интерфейса комбинация работает лучше всего: используйте композитный для повторного использования логики, базовый компонент для шаблона и стиля, а затем клонируйте этот базовый компонент для вариантов, импортируя и обертывая его.
Пример из реального мира: создание многоразовой системы уведомлений
Представьте, что вам нужно отображать уведомления в вашем приложении: успех, ошибку, предупреждение и информацию. Каждый вариант имеет одинаковую компоновку (иконка, сообщение, кнопка закрытия), но отличается цветом и значком. Используя шаблон прототипа, вы можете создать компонент , а затем клонировать его для каждого типа.
- Определите с реквизитом для , и слотами для значка. Включите логику для автоустранения и анимации.
- Создайте композитный , который управляет очерёдностью уведомлений (аналогично пулу прототипов).
- Создавайте отдельные компоненты уведомлений, такие как , которые расширяют или составляют с зеленой цветовой схемой и значком галочки.
- Регистрируйте эти компоненты глобально или используйте динамический компонент на основе типа.
Преимущество: все уведомления имеют одинаковые атрибуты отклонения логики, перехода и доступности.Изменение базового компонента (например, добавление 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 для понимания сообщества.