Мне никогда не нравилась такая коммуникация событий между родительским и дочерним компонентами, которая заставляет вас передавать опору дочернему, а затем выдавать значение с помощью ключа 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