В версии 2.5.0 Vue значительно улучшил систему объявления типов, чтобы лучше использовать объектный API по умолчанию.
значит когда мыПросто файл объявления для установки Vue, все также будет работать как положено:
-
this, то есть Vue; -
thisВ атрибуте у него есть атрибут функции с тем же именем, который определен в опции «Методы»; - Свойства/методы, определенные для данных экземпляра, вычисленные, реквизиты, также появятся в
thisатрибут; - ......
В этой статье давайте поговорим об истории вышеизложенного.
Methods
Когда мы создаем экземпляр Vue и определяем методы в Methods,thisИмеет не только свойство экземпляра Vue, но и свойство функции с тем же именем, что и у параметра Methods:
new Vue({
methods: {
test () {
this.$el // Vue 实例上的属性
}
},
created () {
this.test() // methods 选项上同名的方法
this.$el // Vue 实例上的属性
}
})
Для того, чтобы исследовать его принцип, у нас есть возможность объявления компонентов, переписанных следующим образом:
Определить методы:
// methods 是 [key: string]: (this: Vue) => any 的集合
type Methods = Record<string, (this: Vue) => any>
Возникнет проблема с методами, определенными в Methods.this, которые являются всеми методами конструктора Vue, без доступа к нашим пользовательским методам.
Нам нужно передать экземпляр Vue:
type Methods<V> = Record<string, (this: V) => any>
Параметры компонента (также необходимо передать экземпляр):
interface ComponentOption<V> {
methods: Methods<V>,
created?(this: V): void
}
Мы можем использовать его:
declare function testVue<V extends Vue>(option: ComponentOption<V>): V
В этом случае мы должны явно передать тип экземпляра компонента, чтобы он скомпилировался:
interface TestComponent extends Vue {
test (): void
}
testVue<TestComponent>({
methods: {
test () {}
},
created () {
this.test() // 编译通过
this.$el // 通过
}
})
Это немного проблематично, чтобы заставить его работать в соответствии с нашей ожидаемой работой, мы определяем дополнительный интерфейс.
В файле объявления Vue используется простой способ: с помощьюThisType<T>тип карты, пустьthisобладает необходимыми свойствами.
в репозитории TypeScriptThisType<T>изPR Ниже приведен пример использования:
В этом примере, используя значение методовThisType<D & M>, поэтому TypeScript выводит объект методов вthisТо есть:{ x: number, y: number } & { moveBy(dx: number, dy: number ): void }.
Точно так же мы можем позволитьthisИмеет атрибут функции с тем же именем, определенный в методах:
type DefaultMethods<V> = Record<string, (this: V) => any>
interface ComponentOption<
V,
Methods = DefaultMethods<V>
> {
methods: Methods,
created?(): void
}
declare function testVue<V extends Vue, Methods> (
option: ComponentOption<V, Methods> & ThisType<V & Methods>
): V & Methods
testVue({
methods: {
test () {}
},
created () {
this.test() // 编译通过
this.$el // 实例上的属性
}
})
В приведенном выше коде мы:
- Создан интерфейс ComponentOption, который имеет два параметра: текущий экземпляр Vue и значение по умолчанию
[key: string]: (this: V) => anyМетоды. - Определяет функцию testVue, передавая общий V, Methods в ComponentOption и
ThisType.ThisType<V & Methods>отметить в экземпляреthisТо есть пересечение V и Methods. - Когда вызывается функция testVue, TypeScript выводит методы как
{ test (): void }, так что внутри экземпляраthisТо есть:Vue & { test (): void };
Data
благодаря вышеизложенномуThisType<T>, обработка данных аналогична методам, с той лишь разницей, что данные могут иметь два разных типа: объект или функция. Его тип записывается следующим образом:
type DefaultData<V> = object | ((this: V) => object)
Точно так же мы немного модифицировали ComponentOption и testVue.
interface ComponentOption<
V,
Data = DefaultData<V>,
Methods = DefaultMethods<V>
> {
data: Data
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Methods> (
option: ComponentOption<V, Data, Methods> & ThisType<V & Data & Methods>
): V & Data& Methods
Когда Data является объектом, он отлично работает:
testVue({
data: {
testData: ''
},
created () {
this.testData // 编译通过
}
})
Когда мы передаем функцию, это не так:
TypeScript делает вывод, что данные(() => { testData: string }), что не ожидается{ testData: string }, нам нужно немного изменить тип опций параметра функции.Когда данные передаются как функция, возьмите возвращаемое значение функции:
declare function testVue<V extends Vue, Data, Method>(
option: ComponentOption<V, Data | (() => Data), Method> & ThisType<V & Data & Method>
): V & Data & Method
На данный момент компиляцию можно выполнить:
testVue({
data () {
return {
testData: ''
}
},
created () {
this.testData // 编译通过
}
})
Computed
Обработка Computed кажется немного сложной: она отличается от Methods, когда мы определяем метод в Methods,thisтакже будет содержать свойство функции с тем же именем, тогда как при определении метода в Computed с возвращаемым значением мы ожидаемthisСвойство с тем же именем, которое содержит возвращаемое значение функции.
Например:
new Vue({
computed: {
testComputed () {
return ''
}
},
methods: {
testFunc () {}
},
created () {
this.testFunc() // testFunc 是一个函数
this.testComputed // testComputed 是 string,并不是一个返回值为 string 的函数
}
})
Нам нужен тип сопоставления, который сопоставляет функцию, определенную в Computed с возвращаемым значением, с новым типом, где ключ — это имя функции, а значение — возвращаемое значение функции:
type Accessors<T> = {
[K in keyof T]: (() => T[K])
}
Accessors<T>сопоставит тип T с новым типом с тем же именем свойства и значением, что и возвращаемое значение функции, во время вывода типа процесс будет обратным.
Затем мы добавляем приведенный выше пример:
// Computed 是一组 [key: string]: any 的集合
type DefaultComputed = Record<string, any>
interface ComponentOption<
V,
Data = DefaultData<V>,
Computed = DefaultComputed,
Methods = DefaultMethods<V>
> {
data?: Data,
computed?: Accessors<Computed>
methods?: Methods,
created?(): void
}
declare function testVue<V extends Vue, Data, Compted, Methods> (
option: ComponentOption<V, Data | (() => Data), Compted, Methods> & ThisType<V & Data & Compted & Methods>
): V & Data & Compted & Methods
testVue({
computed: {
testComputed () {
return ''
}
},
created () {
this.testComputed // string
}
})
При вызове testVue мы передаем свойство какtestComputed () => ''Вычислено, TypeScript попытается сопоставить тип сAccessors<T>, поэтому можно сделать вывод, что Computed{ testComputed: string }.
Кроме того, в Computed есть и другая форма записи: форма get и set, нам нужно только соответствующим образом дополнить тип отображения:
interface ComputedOptions<T> {
get?(): T,
set?(value: T): void
}
type Accessors<T> = {
[K in keyof T]: (() => T[K]) | ComputedOptions<T[K]>
}
Prop
в предыдущей статьеНекоторые мысли об использовании TypeScript в Vue (практика)В том, что мы уже обсудили вывод опоры и не повторим их здесь.
наконец
Эта статья представляет собой простую интерпретацию типизации Vue. Я надеюсь, что когда вы поймете исходный код, не забудете типизацию Vue. В конце концов, типизация Vue — это ключ к побуждению и ограничению поведения программы.
Ссылаться на
- https://github.com/Microsoft/TypeScript/pull/14141
- http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#mapped-types
- https://github.com/vuejs/vue/blob/dev/types/options.d.ts