Как написать файл объявления Typescript

внешний интерфейс TypeScript

использоватьTypeScriptПрошло некоторое время, и это действительно хорошая вещь, хотя я нашел кое-что в процессе его использования.bug, но есть небольшие проблемы, так что общее впечатление все еще очень хорошее.

TypeScriptпочему это называетсяType, неотделим от его строгой типизации, которая также отличается отJavaScriptНаиболее важным моментом является то, что объявление типа может быть записано непосредственно в коде или может быть написан отдельный файл описания для представления типа.*.d.ts.

общий путь

первый вd.tsВ файле объявления не будет каких-то простых определений базовых типов (поскольку они записываются после выражений и переменных, а определения здесь не имеют смысла), а типы, определенные в файлах объявлений, часто представляют собой сложные структуры.

Большая часть грамматики такая же, как и в обычномtsСинтаксис в файле тоже соответствуетexportДалее следуют элементы для экспорта.

Проще всего использоватьtypeключевое слово для определения:

type A = {                 // 定义复杂结构
  b: number
  c: string
}

type Func = () => number   // 定义函数

type Key = number | string // 多个类型

Тип комбинации

И вTypeScriptЕсть очень простой способ нацелитьсяtypeНапример, для повторного использования у нас естьAnimalтип, аDogтип, вы можете использовать&для повторного использования.

P.S> &Символы могут быть объединены в несколько

type Animal = {
  weight: number
  height: number
}

type Dog = Animal & {
  leg: number
}

Динамическое назначение типа JSON

если у нас естьJSONструктуру, и ееkeyявляется динамическим, то мы, конечно, не можем объединить всеkeyВсе прописано в коде, нам нужно только просто указать подстановочный знак:

type info = {
  [k: string]: string | number // 可以指定多个类型
}

const infos: info = {
  a: 1,
  b: '2',
  c: true, // error 类型不匹配
}

А в новой версии больше рекомендуется использовать встроенные функцииRecordреализовать:

const infos: Record<string, string | number> = {
  a: 1,
  b: '2',
  c: true, // error
}

Получить тип переменной

Предположим, у нас есть объект JSON, который содержитname,ageдва свойства, мы можем передать некоторыеTypeScriptВстроенные служебные функции, чтобы делать некоторые интересные вещи.

пройти черезkeyofа такжеtypeofКомбинируя, чтобы получить желаемый результат:

const obj = {
  name: 'Niko',
  age: 18
}

// 如果是这样的取值,只能写在代码中,不能写在 d.ts 文件中,因为声明文件里边不能存在实际有效的代码
type keys = keyof typeof obj

let a: keys = 'name' // pass
let b: keys = 'age'  // pass

let c: keys = 'test' // error

И если мы хотим преобразовать неоднородный типJSONИзменено на единый типJSONВы также можете использовать этот способ:

const obj = {
  name: 'Niko',
  age: 18,
  birthday: new Date()
}

const infos: Record<keyof typeof obj, string> = {
  name: '',
  age: '',
  birthday: 123, // 出错,提示类型不匹配
  test: '', // 提示不是`info`的已知类型
}

Получить возвращаемый тип функции

Допустим, у нас есть функция, которая возвращаетJSON, а нам это нужноJSONбыть типом.

затем черезReturnType<>реализовать:

function func () {
  return {
    name: 'Niko',
    age: 18
  }
}

type results = ReturnType<typeof func>

// 或者也可以拼接 keyof 获取所有的 key
type resultKeys = keyof ReturnType<typeof func>

// 亦或者可以放在`Object`中作为动态的`key`存在
type infoJson = Record<keyof ReturnType<typeof func>, string>

объявить функции в коде иclassТипы

потому что мы знаем, что функция иclassНа момент создания есть актуальный код (тело функции, конструктор).
Но мы пишемd.tsВ файле объявления это просто типоспецифичное ограничение, так что реального кода точно не будет, но если в обычномtsЗаписывать это в файл будет неправильно, поэтому для такой ситуации нам нужно использоватьdeclareКлючевое слово означает, что мы здесь, чтобы определить тип, а не объект или функцию:

class Personal {
  name: string
  // ^ 出错了,提示`name`必须显式的进行初始化
}

function getName (personal: Personal): name
// ^ 出错了,提示函数缺失实现

Правильное использование выглядит следующим образом:

-declare class Personal {
+declare class Personal {
  name: string
}

-function getName (personal: Personal): name
+declare function getName (personal: Personal): name

Конечно, это определение обычно не рекомендуется.classДа, вы должны использоватьinterfaceзаменить вот такclassдолжны существовать только для не-TSОписание модуля, если это модуль собственной разработки, то сама структура имеет признаки объявления типа.

перегрузка функций

Эта концепция доступна только в некоторых строго типизированных языках.TypeScript, это также строго типизированный язык, поэтому будут места, где необходимо использовать такие объявления.

Например, у нас естьaddфункция, которая может получитьstringПараметры типа сращиваются, а также могут получатьnumberДобавляются параметры типа.

Следует отметить, что его можно разместить только в определении перегрузки функции стороннего подключаемого модуля.d.tsфайл, в других средах рекомендуется объединить определение и реализацию функции (хотя конфигурацияpathsОтдельная обработка также может быть достигнута, но тогда ограничения на создание функции теряются)

// index.ts

// 上边是声明
function add (arg1: string, arg2: string): string
function add (arg1: number, arg2: number): number
// 因为我们在下边有具体函数的实现,所以这里并不需要添加 declare 关键字

// 下边是实现
function add (arg1: string | number, arg2: string | number) {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 arg1 + arg2
  if (typeof arg1 === 'string' && typeof arg2 === 'string') {
    return arg1 + arg2
  } else if (typeof arg1 === 'number' && typeof arg2 === 'number') {
    return arg1 + arg2
  }
}

TypeScriptПерегрузка функции — это просто объявление нескольких функций, и конкретная логика должна быть написана вами самостоятельно, так как она не будет объединять тела ваших нескольких функций с одним и тем же именем.

Порядок нескольких функций

Представьте, если бы у нас была функция, которая передается вDateпараметр типа, вернуть егоunixвременная метка, если она переданаObject, то конкретный тип объектаtoStringвывод и прямой возврат в других случаях Как должна быть написана такая функция?

Просто для примера демонстрация, нормальные люди такую ​​функцию не напишут...

function build (arg: any) {
  if (arg instanceof Date) {
    return arg.valueOf()
  } else if (typeof arg === 'object') {
    return Object.prototype.toString.call(arg)
  } else {
    return arg
  }
}

Однако такая перегрузка функций очень требовательна к порядку объявления, и функция с высокой точностью должна быть помещена первой:

// 这样是一个错误的示例,因为无论怎样调用,返回值都会是`any`类型
function build(arg: any): any
function build(arg: Object): string
function build(arg: Date): number

потому чтоTypeScriptНайдя объявление о перегрузке функции, она остановится и не будет продолжать поиск.anyявляется наиболее неоднозначным диапазоном, в то время какObjectсодержит сноваDate, поэтому мы должны расположить их в порядке от меньшего к большему:

function build(arg: Date): number
function build(arg: Object): string
function build(arg: any): any

// 这样在使用的时候才能得到正确的类型提示
const res1 = build(new Date()) // number
const res2 = build(() => { })  // string
const res3 = build(true)       // any

Некоторые сценарии, не требующие перегрузки функций

Смысл перегрузки функции в том, чтобы вы знали, что разные параметры передаются для получения разных результатов.Если переданные параметры разные, полученные результаты (Типы), но то же самое, то не используйте здесь перегрузку функций (это не имеет смысла).

Если возвращаемый тип функции одинаков, то нет необходимости использовать перегрузку функции.

function func (a: number): number
function func (a: number, b: number): number

// 像这样的是参数个数的区别,我们可以使用可选参数来代替函数重载的定义
function func (a: number, b?: number): number
// 注意第二个参数在类型前边多了一个`?`

// 亦或是一些参数类型的区别导致的
function func (a: number): number
function func (a: string): number

// 这时我们应该使用联合类型来代替函数重载
function func (a: number | string): number

Interface

interfaceвTypeScriptуникальный вJavaScriptнисколькоinterfaceСкажи это.
потому чтоinterfaceиспользуется только для уточнения его реализацииclassСоответствующее поведение без какого-либо реального кода является недопустимой операцией для языка сценариев.

грамматически сclassне большая разница, но вinterfaceТолько свойства-члены могут быть объявлены вfunctionВы можете писать только конкретные полученные параметры и тип возвращаемого значения, а не вinterfaceНапишите конкретное тело функции вinterfaceНазначение в:

// 这是一个错误的示例
interface PersonalIntl {
  name: string = 'Niko'

  sayHi (): string {
    return this.name
  }
}

// 在 interface 中只能存在类型声明
interface PersonalIntl {
  name: string

  sayHi (): string
}

Действительно, в некоторых случаях используютinterfaceс обычнымtypeОпределения не имеют значения.
Например, мы хотим экспортировать существованиеnameа такжеageОбъект с двумя свойствами:

// types/personal.d.ts
export interface PersonalIntl {
  name: string
  age:  number
}

// index.d.ts
import { PersonalIntl } from './types/personal'

const personal: PersonalIntl = {
  name: 'Niko',
  age:  18,
}

еслиinterfaceзаменитьtypeС определением тоже все в порядке:

// types/personal.d.ts
export type PersonalIntl = {
  name: string
  age:  number
}

Такое определение прекрасно подходит для использования на основе вышеизложенного, но оно применимо только кObjectБуквальное объявление, нет способа хорошо ограничить егоclassрежим, поэтому мы используемinterfaceограничиватьclassРеализация:

import { PersonalIntl } from './types/personal'

class Personal implements PersonalIntl {
  constructor(public name: string, public age: number) { }

  // 上边的简写与下述代码效果一致

  public name: string
  public age: number

  constructor (name: string, age: number) {
    this.name = name
    this.age = age
  }
}

const personal = new Personal('niko', 18)

Некоторые сомнения по поводу объявлений функций-членов

Во-первых, есть два способа определить функцию в интерфейсе: один в экземпляре и один в цепочке прототипов.
Два объявления выглядят следующим образом:

interface PersonalIntl {
  func1 (): any      // 原型链方法

  func2: () => any   // 实例属性
}

Но когда мы реализуем эти два свойства, мы можем фактически конвертировать друг друга, и нет строгого требования, какой метод должен использоваться:

class Personal implements PersonalIntl {
  func1 () {
    console.log(this)
  }

  func2 = () => {
    console.log(this)
  }
}

На самом деле, после компиляции двухJavaScriptЕсть разница в коде, не понятно что этоbugИли конструкция такая, структура такая:

var Personal = /** @class */ (function () {
    function Personal() {
        var _this = this;
        this.func2 = function () {
            console.log(_this);
        };
    }
    Personal.prototype.func1 = function () {
        console.log(this);
    };
    return Personal;
}());

Поэтому рекомендуется соблюдатьinterfaceОпределенный способ создания, позволяющий избежать некоторых странных проблем, которые могут существовать.

Автоматическое слияние объявлений интерфейса

потому чтоinterfaceдаTypeScriptУникальный, так что также будут некоторые интересные функции, такие как то же имяinterfaceбудут объединены автоматически:

interface PersonalIntl {
  name: string
}

interface PersonalIntl {
  age: number
}

class Personal implements PersonalIntl {
  name = 'Niko'
  age = 18
}

Не используйте перегрузку функций в интерфейсах

существуетinterfaceЕсли вы используете перегрузку функций вbuildфункция, еслиinterfaceобъявлено в , а затем вclassРеализовано, то как бы ни вызывался, будет учитываться тип возвращаемого значенияany.

Итак, правильный путьclassОбъявите о перегрузках вclassреализовано вinterfaceопределить не более одногоany, вместо трех перегрузок.

class Util implements UtilIntl {
  build(arg: Date): number
  build(arg: Object): string
  build(arg: any): any

  build(arg: any) {
    if (arg instanceof Date) {
      return arg.valueOf()
    } else if (typeof arg === 'object') {
      return Object.prototype.toString.call(arg)
    } else {
      return arg
    }
  }
}

резюме

СвязанныйTypeScriptОбъявления типа объявления, относящиеся к этим наиболее часто используемым, в настоящее время суммированы, и друзья могут добавить их.

существовал в предыдущих версияхmoduleа такжеnamespaceОпределение , но в настоящее время кажется, что более рекомендуется версия ES-Modules.import/exportДля достижения аналогичных функций, а не пользовательского синтаксиса, поэтому описание этих двух ключевых слов пропускается.

В официальной документации есть шаблон того, как написать файл объявления, вы можете обратиться к:массив телепортации

использованная литература