По мере усложнения приложения, в котором львиная доля кода использует Vue.js, возникает вопрос повторного использования кода. Когда возможности расширить тот или иной компонент с помощью параметров или слотов уже не удовлетворяют всех потребностей, можно прибегнуть в другим способам.
ОБНОВЛЕНИЕ 2024-05-07. ВНИМАНИЕ! Если вы интересуетесь темой повторного использования кода Vue-компонентов (в том числе шаблона), то вам лучше начать с обзорной статьи Есть ли наследование во Vue.js.
Популярный, но все всегда правильный, способ использовать некоторую сущность добавив новые возможности – использовать наследование.
Mixin
У Vue.js для возможности наследования имеются mixin-ы. В mixin мы можем вынести данные, группу методов и даже hook-и жизненного цикла.
Например, создадим mixin-ы decrementMixin.js:
export default {
data() {
return {
mixinData1: 1,
}
},
created() {
console.info(`Mixin's (1) hook "created"`);
},
methods: {
decrement() {
this.index--;
}
}
};
и incrementMixin.js
export default {
data() {
return {
mixinData2: 2,
}
},
created() {
console.info(`Mixin's (2) hook "created"`);
},
methods: {
increment() {
this.index++;
},
}
};
Теперь можно создать компонент, который будет использовать эти mixin-ы
Mix.vue:
<template>
<div class="component-border pl-1 mb-1 ms-1">
<h1>
{{ title }}
</h1>
<div>
<p>mixinData1: {{ mixinData1 }}</p>
<p>mixinData2: {{ mixinData2 }}</p>
<p>Data: {{ index }}</p>
<p>
<button
v-for="(action, index) in actions"
v-bind:key="index"
@click="action.handler"
class="me-1">
{{ action.text }}
</button>
</p>
</div>
</div>
</template>
import incrementMixin from "@/mixins/incrementMixin"
import decrementMixin from "@/mixins/decrementMixin";
export default {
name: 'Mix',
mixins: [ incrementMixin, decrementMixin ],
data() {
return {
title: 'Mix',
index: 0,
actions: [
{ text: 'Increment', handler: this.increment },
{ text: 'Decrement', handler: this.decrement },
{ text: 'Clear', handler: this.clear },
]
}
},
created() {
console.info(`Mix component hook "created"`);
},
methods: {
clear() {
this.index = 0;
},
}
}
Данный компонент будет иметь методы из обоих миксинов. Обработчики created будут выполнены по очереди. Выглядеть на странице будет так:
В консоли при загрузке странице можно будет увидеть следующее:
# Mixin's (2) hook "created"
# Mixin's (1) hook "created"
# Mix component hook "created"
Можно видеть, что отрабатывают все hook-и жизненного цикла, что определены.
Шаблон для отображения используется, который описана в компоненте, так как в миксинах нет шаблонов. Таким образом с помощью мискинов как из частей можно создать компонент, который будет состоять из своих данных и методов и данных и методов полученных у миксинов.
Extends
Если же нужно создать потомка от существующего компонента, то для этого есть опция extends.
Допустим, у нас есть компонент Parent.vue:
<template>
<div class="component-border pl-1 mb-1 ms-1">
<h1>
{{ title }}
</h1>
<div>
<p>Param: {{ param }}</p>
<p>Data: {{ index }}</p>
<p>
<button
v-for="(action, index) in actions"
v-bind:key="index"
@click="action.handler"
class="me-1">
{{ action.text }}
</button>
</p>
</div>
</div>
</template>
export default {
name: 'Parent',
props: {
param: { type: String, default: '' },
},
data() {
return {
title: 'Parent',
index: 0,
actions: [
{ text: 'Increment', handler: this.increment },
]
}
},
created() {
console.info(`Parent's hook "created" for component "${this.title}"`);
},
methods: {
clear() {
this.index = 0;
},
increment() {
this.index++;
},
}
}
Унаследуем от него ChildExtend.vue:
import Parent from "./Parent"
export default {
name: 'ChildExtend',
extends: Parent,
data() {
return {
title: 'ChildExtend',
actions: [
{ text: 'Increment', handler: this.increment },
{ text: 'Decrement', handler: this.decrement },
{ text: 'Clear', handler: this.clear },
]
}
},
created() {
console.info(`Child's hook "created"`);
},
methods: {
decrement() {
this.index--;
}
}
}
Шаблона собственного у него нет. Он использует шаблон родителя. Ели определим в потомке шаблон, то он полностью заменит родительский.
Вместе родительский компонент и наследник выглядят на странице так:
По своей сути extends – тот же mixin, только применяется к компоненту и позволяет наследовать шаблон.
Higher-order components (HOC)
На этом способы повторного использования кода Vue.js не заканчиваются. В сети можно встретить описание подхода Higher-order components (HOC). Он похож на паттерн “декоратор”.
Принцип такой. У исходного компонента есть некое действие (метод). А компонент, который использует исходный вызывает исходное действие и добавляет свое поведение.
Обычно в примерах HOC приводят логирование. Если вам довелось использовать такой подход в чем-то более полезном, пишите в комментариях. Я пока не вижу у данного подхода возможность широкого применения.
Итак, классический пример. Допустим у нас есть кнопка BaseButton.vue:
<template>
<button v-on:click="increment"><slot/> [<span>{{ index }}]</span></button>
</template>
<script>
export default {
name: 'BaseButton',
data() {
return {
index: 1,
}
},
methods: {
increment() {
this.index++;
}
}
}
</script>
Используем ее так:
<base-button>Base button</base-button>
Можем создать функцию, которая позволяет добавить поведение. Например, здесь добавлено логирование click-ов по кнопке.
export const withLoggerButton = button => {
return {
name: 'ExtendedButton',
render(h) {
const slots = Object.keys(this.$slots).map(key => this.$slots[key]);
return h(
button,
{
nativeOn: {
click:() => {
console.log('clicked')
}
}
},
slots
);
}
}
}
Для лучшего понимания данного примера нужно быть знакомым с документацией по render функциям. Особенно подраздел “Подробно об объекте данных”.
После того, как была написана функция withLoggerButton мы можем где-либо создать компонент – ExtendedButton:
import BaseButton from "@/components/BaseButton";
import { withLoggerButton} from "@/withLoggerHOC";
const ExtendedButton = withLoggerButton(BaseButton);
И использовать его так:
<extended-button>Extended Button with logger</extended-button>
Расширенная кнопка будет работать точно также как и базовая, но отправлять при клике сообщение в консоль браузера.
К сожалению объект данных, передаваемый в функцию h (Сокращение createElement до h — распространённое соглашение в экосистеме Vue и обязательное для использования JSX. ) довольно ограниченный. В связи с этим в большой пользе от такого способа повторного использования кода я сильно сомневаюсь.
На этом статью завершу. Все примеры можно посмотреть и запустить склонировав код с github репозитория.
Но также я планирую продолжить изучение способов повторного использования кода в Vue.js и скоро вернусь с новой статьей.
После публикации данной статьи появились новые статьи с продолжением темы: