См. «это» из типизации Vue

внешний интерфейс TypeScript Vue.js
См. «это» из типизации Vue

В версии 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