Как сделать значение модели v-model необязательным в Vue.js
При написании моей библиотеки пользовательского интерфейса Vue.js, Inkline, мне пришлось найти способ заставить некоторые компоненты работать как с указанием значения модели ( v-model
), так и без него. Хотя это не распространенный сценарий, вы обязательно столкнетесь с ним, если пишете библиотеку и серьезно относитесь к Developer Experience (DX).
Я называю их необязательно контролируемыми компонентами, потому что они должны работать из коробки без предоставления v-model
, но предоставят вам полный контроль над их состоянием, если вы предоставите v-model
.
Пример меню
Одним из ярких примеров необязательно управляемого компонента может быть меню, которое можно открыть (развернуть) или закрыть (свернуть). Назовем компонент просто MyMenu
.
С точки зрения взаимодействия с разработчиками вы, вероятно, захотите, чтобы пользователь вашей библиотеки мог вставить <my-menu>
в свой код и сразу же начать добавлять сворачиваемый контент, не беспокоясь об обработке его открытого или закрытого состояния.
Вот как бы компонент выглядел без поддержки v-model
:
<template>
<div class="my-menu">
<button @click="toggleMenu">
Menu
</button>
<menu v-show="open">
<slot />
</menu>
</div>
</template>
<script>
export default {
name: 'MyMenu',
data() {
return {
open: false
};
},
methods: {
toggleMenu() {
this.open = !this.open;
}
}
}
</script>
Необязательное значение модели
Все идет нормально. Рассмотрим следующий сценарий: ваш пользователь хочет иметь возможность открывать или закрывать меню из другого места. Мы знаем, что на этом этапе мы можем открывать и закрывать меню изнутри, но как мы позволим пользователю библиотеки дополнительно контролировать состояние?
Я нашел перспективное решение, которое избавит вас от многих проблем. Вот как это выглядит:
<template>
<div class="my-menu">
<button @click="toggleMenu">
Menu
</button>
<menu v-show="open">
<slot />
</menu>
</div>
</template>
<script>
export default {
name: 'MyMenu',
emits: [
'update:modelValue'
],
props: {
modelValue: {
type: Boolean,
default: false
}
},
data() {
return {
open: this.modelValue
};
},
methods: {
toggleMenu() {
this.open = !this.open;
this.$emit('update:modelValue', this.open);
}
},
watch: {
modelValue(value) {
this.open = value;
}
}
}
</script>
Попробуйте простой пример в прямом эфире на CodeSandbox.
Вы можете видеть выше, что я добавил обычную опору modelValue
для поддержки v-model
в Vue 3, но в основном я сделал три вещи:
- Я устанавливаю начальное значение нашего внутреннего свойства состояния
open
равным значению, предоставленному черезv-model
. Это творит чудеса, потому что, когда нетv-model
, она будет равна указанному значению по умолчанию, в нашем случаеfalse
. - Я генерирую событие
update:modelValue
каждый раз, когда меняю внутреннее значениеthis.open
- Я добавил событие, который гарантирует, что я всегда синхронизирую внутреннее значение
open
с входящей внешней опоройmodelValue
.
Вывод
Классно, не правда ли? Важно никогда не забывать об опыте разработчика. Такая мелочь, как эта, может сэкономить драгоценные часы времени на разработку, если все будет сделано правильно и последовательно.
Надеюсь, сегодня вы узнали что-то интересное. Удачного кодирования!