Мне никогда не нравилась такая коммуникация событий между родительским и дочерним компонентами, которая заставляет вас передавать опору дочернему, а затем выдавать значение с помощью ключа update:<prop_name>
, поэтому я решил найти лучшее решение этой проблемы с помощью небольшой магии машинописного текста.
Пример создания
Давайте создадим простой пример с родительскими и дочерними компонентами.
Вот родительский компонент:
<template> <HelloWorld :msg.sync="msg"/> </template> <script lang="ts"> export default Vue.extend({ name: 'App', data: () => ({ msg: 'Marko', }), }); </script>
и дочерний компонент
<template> <v-container> <v-layout text-center wrap> <v-flex>{{msg}}</v-flex> </v-layout> </v-container> </template> <script lang="ts"> @Component export default class HelloWorld extends Vue{ @Prop({required: true, type: String,}) public msg!: string; } </script>
Отказ от префикса «update:»
Прежде всего, я хотел отказаться от префикса update:
. Я начал с расширения Vue.prototype новой функцией, назовем ее $sync
и объявим в main.ts вот так (подробнее о расширении Vue.prototype здесь):
Vue.prototype.$sync = function(key: string, value: any) { this.$emit(`update:${key}`, value); };
Хорошо, теперь мне нужно сообщить машинописному тексту, что теперь у класса Vue есть функция $ sync. Для этого мне нужно создать vue.d.ts
в папке src/types
.
declare module 'vue/types/vue' { interface Vue { $sync(key: string, value: any): void; } }
Мы изменим эту строку и любую последнюю. Теперь мы можем добавить кнопку, которая использует нашу новую функцию $ sync следующим образом:
<template> <v-container> <v-layout text-center wrap> <v-flex>{{msg}}</v-flex> <v-btn depressed primary @click="respond()">Respond</v-btn> </v-layout> </v-container> </template> <script lang="ts"> @Component export default class HelloWorld extends Vue{ @Prop({required: true, type: String,}) public msg!: string; public respond(){ this.$sync('msg', `${this.msg} Polo`); } } </script>
Добавление типов для ключа $ emit
Хорошо, теперь я хотел бы получить IntelliSense из IDE для нашего ключа, а также изменить $sync
для универсального метода, чтобы ввести значение, которое излучает.
Здесь мы можем использовать мое любимое ключевое слово keyof, чтобы лучше печатать. Итак, давайте немного изменим наш vue.d.ts
, чтобы он выглядел так:
declare module 'vue/types/vue' { interface Vue { $sync(key: keyof this, value: any): void; } }
Хорошо, теперь мы получаем отличное автозаполнение кода, а также компилятор машинописного текста может проверить нашу переданную строку
Как видите, здесь предлагается использовать строку msg
в качестве параметра. Но когда мы хотим использовать что-то другое, например msg1
, мы получаем ошибку компилятора.
Итак, теперь мы можем передать только ту опору, которая существует и объявлена в нашем классе, расширяющем Vue.
Generic $ sync
Последнее, что нужно добавить, это просто сделать этот метод $ sync универсальным и, наконец, объявить его следующим образом:
declare module 'vue/types/vue' { interface Vue { $sync<T>(key: keyof this, value: T): void; } }
Итак, вот наш последний пример дочернего компонента:
<template> <v-container> <v-layout text-center wrap> <v-flex> {{msg}} </v-flex> <v-btn depressed primary @click="respond()">Respond</v-btn> </v-layout> </v-container> </template> <script lang="ts"> import {Component, Prop} from 'vue-property-decorator'; import Vue from 'vue'; @Component export default class HelloWorld extends Vue{ @Prop({required: true, type: String,}) public msg!: string; public respond(){ this.$sync<string>('msg', `${this.msg} Polo`); } } </script>
и каждый раз мы будем использовать этот компонент вот так
<HelloWorld :msg.sync="variable"/>
мы получим обновленную переменную со всей поддержкой автозавершения ключей и проверкой типов. Полный пример здесь в моей ветке репозитория $ sync