Vue3 开发过程中使用到的TS知识点

536 min read

在线测试

https://www.typescriptlang.org/zh/play

类型标注

typescript中我们可以给变量标记类型, 后续的代码中ts会自动校验变量是否有错误的读/写操作.

let is:boolean;
is =  true;
is =  123; // 报错, 提示number类型不能赋值给boolean类型的变量

语法很简单, 就是在变量后面加上":"和类型, 这个动作叫类型标注.

类型自动推断

如果变量的值是一个字面量, ts可以自动推断出类型.

let is = false;
is = true;
is =  123; // 报错, 提示number类型不能赋值给boolean类型的变量

let o = {a:1,b:'2'}
o.a ='33' // 报错, 不能吧string分配给number

boolean

let is:boolean;
is =  true;
is =  123; // 报错, 提示is是数字不能赋值给boolean类型的变量

number ,不仅仅支持10进制.

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
decLiteral = '123'; // 报错

string

let s1:string = 'hello world!'; 
let s2:string = 'hello ${name}`;
s1 = 123 // 报错

数组 "类型[]"

let numbers:number[] = [1,2,3,4,5];

// number|string代表联合类型, 下面的高级类型中会讲
let numbers:(number|string)[] = [1,2,3,4,'5'];

数组 Array<类型>

let numbers:Array<number> = [1,2,3,4,5];

Tuple 元组类型

表示一个已知元素数量类型数组, 各元素的类型不必相同:

let list1:[number, string] = [1, '2', 3]; // 错误, 数量不对, 元组中只声明有2个元素
let list2:[number, string] = [1, 2]; // 错误, 第二个元素类型不对, 应该是字符串'2'
let list3:[number, string] = ['1', 2]; // 错误, 2个元素的类型颠倒了
let list4:[number, string] = [1, '2']; // 正确

枚举(enum) 枚举是ts中有而js中没有的类型, 编译后会被转化成对象, 默认元素的值从0开始, 如下面的Color.Red的值为0, 以此类推Color.Green为1, Color.Blue为2:

enum Color {Red, Green, Blue}
// 等价
enum Color {Red=0, Green=1, Blue=2}

我们还可以反向通过值得到键:

enum Color {Red=1, Green=2, Blue=4}
Color[2] === 'Green' // true

编译成js后的枚举代码

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
// Color的值为: {0: "Red", 1: "Green", 2: "Blue", Red: 0, Green: 1, Blue: 2}

any

any代表任意类型, 也就是说, 如果你不清楚变量是什么类型, 就可以用any进行标记, 比如引入一些比较老的js库, 没有声明类型, 使用的时候就可以标记为any类型, 这样ts就不会提示错误了. 当然不能所有的地方都用any, 那样ts就没有使用的意义了.

void

void的意义和any相反, 表示不是任何类型, 一般出现在函数中, 用来标记函数没有返回值:

function abc(n:number):void{
    console.log(n);
}

void类型对应2个值, 一个是undefined,一个null:

const n1:void = undefined;
const n2:void = null;

null 和 undefined

默认情况下null和undefined是所有类型的子类型, 比如:

const n1:null = 123;
const n2:undefined = '123';

never never表示不可达, 用在throw的情况下:

function error():never{
    throw '错了!';
}

unknown

unknown表示未知类型, 他和any一样的地方是标记后的变量可以随意赋值, 但是在访问变量上的属性的时候要先断言变量的类型才可以操作.

let a:unknown;
a = '汉字';
a.length // 提示类型为unknown, 不能访问length

类型断言(as或<>)

当操作的时候我都可以强制告诉ts该变量现在是什么类型. 比如在上面的a变量是unknown在ts中我们是没法对他进行操作的, 这时候就需要进行"类型断言":

let a:unknown;
a = '汉字';

(a as string).length

// 等价写法
(<string>a).length

symbol

let s:symbol;
s = Symbol('a');
s = 1 //报错 

object object表示非原始类型, 也就是除number/ string/ boolean/ symbol/ null/ undefined之外的类型:

let o1:object = [];
let o2:object = {a:1,b:2};

自动类型推断(类型保护)

上节我们说ts可以从"字面量"推断出变量类型, ts的这种"自动推断"的能力官方叫做"类型保护".

let o = {a:1,b:'2'} // 可以识别对象中的字段和类型
o.a ='33' // 报错, 不能吧string分配给number

typeof判断类型

let n:number|string = 0.5 < Math.random()? 1:'1';

// 如果没有typeof, n*=2会报错, 提示没法推断出当前是number类型, 不能进行乘法运算
if('number' === typeof n) {
    n*= 2;
} else  {
    n= '2';
}

类型中的typeof 可以提取js对象的类型

function c(n:number){
  return n;
}

// typeof c获取函数c的类型
function d(callback :typeof c){
  const n = callback(1);
  console.log(n);
}

instanceof 判断是否实例.

let obj = 0.5 < Math.random() ? new String(1) : new Array(1);

if(obj instanceof String){
    // obj推断为String类型
    obj+= '123'
} else {
    // obj为any[]类型
    obj.push(123);
}

in判断是否存在字段.

interface A {
  x: number;
}

interface B {
  y: string;
}

function ab(q: A | B) {
  if ('x' in q) {
    // q: A
  } else {
    // q: B
  }
}

自定义类型保护(is)

  1. 首先我们需要定义一个函数, 用来判断并返回boolean值.

  2. 返回值不要直接标注boolean, 而是使用"is", is前面一般是参数, 后面是判断后的类型.

  3. 比如下面的"isBird"函数, 如果返回值是true, 那么"animal"就是Bird.

  4. 配合"if"使用"isBird"时, 会触发自动类型推断.

    interface Animal {
     name: string;
    }
    
    interface Bird {
     name: string;
     hasWing: boolean;
    }
    
    // 关键看函数返回值的类型标注
    function isBird(animal:Animal): animal is Bird {
     return animal.name.includes('雀');
    }
    
    
    // 使用isBird对触发自动类型推断
    const who = { name: '金丝雀' };
    if (isBird(who)) {
     who.hasWing = true;
    }
    

"is"在vue3源码中应用

了解即可.

// 是否是对象
export const isObject = (val: any): val is Record<any, any> =>
  val !== null && typeof val === 'object'

// 是否ref对象
export function isRef(v: any): v is Ref {
  return v ? v[refSymbol] === true : false
}

// 是否vnode
export function isVNode(value: any): value is VNode {
  return value ? value._isVNode === true : false
}

// 是否插槽节点
export const isSlotOutlet = (
  node: RootNode | TemplateChildNode
): node is SlotOutletNode =>
  node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT

接口

一种定义复杂类型的格式, 比如定义对象类型:

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite: string;
}

const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
    fromSite: 'baidu.com'
}

// 报错不存在titleA字段, 同时缺少title/count/content/fromSite
const article: Article = {
    titleA: '为vue3学点typescript(2), 类型',
}

当我们给article赋值的时候, 如果任何一个字段没有被赋值或者字段对应的数据类型不对, ts都会提示错误, 避免字段名拼写错误或漏写.

扩展性

有时对象的字段可能是允许未知的, 比如Article可能还有"a"字段"b"字段, 为了兼容这种情况, 我们改造下:

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite: string;
      [k:string]:string;
}

通过"[k:string]:string"我们允许Article类型中出现任意字符串键值, 只要保证内容也为字符串即可.

非必填(?)

用"?"标记fromSite字段为非必填.

interface Article {
    title: string;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

// 缺少fromSite字段也不会报错
const article: Article = {
    title: '为vue3学点typescript(2), 类型',
    count: 9999,
    content: 'xxx...',
}

只读(readonly)

用"readonly"标记字段不可修改.

interface Article {
    readonly title: string;
    count: number;
    content:string;
    fromSite?: string; // 非必填
}

let a:Article =  {title:'标题',count:1,content:'内容',}
a.title = 123; // 报错, title是只读属性

定义函数

// 声明接口
interface Core {
    (n:number, s:string):[number,string]
}

// 使用接口进行标注
const core:Core = (a,b)=>{
    return [a,b];
}

函数也是对象, 所以接口Core还可以声明属性.

interface Core {
    (n:number, s:string):[number,string];
        n:number;
}

const core:Core = (a,b)=>{
    return [a,b];
}
core.n = 100;

定义类

约束类用"implements"关键字.

// 定义
interface Animal {
    head:number;
    body:number;
    foot:number;
    eat(food:string):void;
    say(word:string):string;
}

// implements
class Dog implements Animal{
    head=1;
    body=1;
    foot=1;

    constructor(head:number){
        this.head = head;
    }
    eat(food:string){
        console.log(food);
    }
    say(word:string){
        return word;
    }
}

对比下可以发现: interface部分指定以了类的实例属性, 而构造函数并没有定义, 定义一下构造函数:

接口的继承

让一个接口拥有另一个接口的所有特性.

interface Animal {
    color: string;
}

interface Cat extends Animal {
    foot: number;
}

let cat:Cat;
cat.color = "blue";
cat.foot = 10;

接口的合并

相同名字的接口会自动合并.

interface Apple {
    color: string;
}
interface Apple {
    weight: number;
}

等价于

interface Apple {
    color: string;
    weight: number;
}

类型别名(type)

通过"type"关键字可以给已有的类型换个名字.

type N = number;
type S = string;

interface F1{
    (a:number):string;
}
type F2 = F1;

定义函数

前面说过接口可以定义函数, type也可以, 但是语法有一点不一样:

interface F1{
    (a:number):string;
}

type F2 = (a:number)=>string;

"F1"和"F2"定义的是相同的类型, 主要参数和返回值中间的他俩的区别, 一个是":"一个是"=>".

定义字面量类型

这是"type"能做到而"interface"不能实现的.

type N = 1|2|3;
type S = 'a'|'b'|'c';

// 正确
const n1:N = 1;
// 错误, 类型N中没有11;
const n2:N = 11

定义联合类型( | )

把多个类型用"|"连接到一起, 表示类型之间"或"的关系.

type F1 = (a:string|number)=>any;
const f1:F1 = (a)=>a;

// 正确
f1(1);
f1('A');

// 错误, 参数类型应该是string或number
f1({a:1});
interface Cat  {
      hand:string;
    foot: string;
}

interface Duck{
    body:string;
}

type Animal  = Cat|Duck;

// 错误, Cat没有body字段
const animal:Cat = {hand:'手',body:'身体',foot:'脚'};
// 正确
const animal:Animal = {hand:'手',body:'身体',foot:'脚'};

定义交叉类型( & )

把多个类型用"&"连接到一起, 取类型的并集.

interface A {a:number};
interface B {b:string};
type AB = A & B;

const a:A = {a:1};
const b:B = {b:'1'};
// 错误, 缺少a字段
const ab1:AB = {b:'2'};
// 正确
const ab2:AB = {a:1,b:'2'};

使用泛型

不用关心类型的类型,或者说成是类型的参数化

function echo<T>(input:T):T{
    return input;
}

泛型函数定义

在函数名后类型变量T, 用"<>"包围, 然后标注函数的参数和返回值的类型都是"T".

虽然使用"echo"的时候参数"input"可以是任意类型, 但是不同于标注"any"类型, 这里函数的返回值会根据参数类型的变化而同步变化.

// n1是number类型
const n1 = echo<number>(1);

// s会推断为string类型
const s = echo<string>('1') 

如果参数是字面量, 那么使用函数的时候可以省略前面的""和"", ts可以自动推断出参数的类型, 从而推断出类型变量T的值:

// n1是number类型
const n1 = echo(1);

// s会推断为string类型
const s = echo('1') 

泛型类定义

在类名后面通过"<>"声明一个类型变量U, 类的方法和属性都可以用这个U类型,

class Person<U> {
    who: U;
    
    constructor(who: U) {
        this.who = who;
    }

    say(code:U): string {
        return this.who + ' :i am ' + code;
    }
}
let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 错误, 会提示参数应该是个string
a.say('007') // 正确

泛型方法定义

和泛型函数的定义方式一样:

class ABC{
    // 输入T[], 返回T
    getFirst<T>(data:T[]):T{
        return data[0];
    }
}

泛型接口定义

interface Goods<T>{
    id:number;
    title: string;
    size: T;
}

// 正确
let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};
let shoes:Goods<number> = {id:1,title: '苹果', size: 43};

泛型接口默认值

type A<T=string> = T[];
// 正确
const a  = ['1','2','3'];

interface Goods<T=string>{
    id:number;
    title: string;
    size: T;
}
// 正确
let apple:Goods<string> = {id:1,title: '苹果', size: 'large'};

泛型约束定义

function echo<T>(input: T): T {
    console.log(input.name); // 报错, T上不确定是否由name属性
    return input;
}

前面说过T可以代表任意类型, 但并不是所有类型上都有"name"字段, 通过"extends"可以约束"T"的范围:

// 现在T是个有name属性的类型
function echo<T extends {name:string}>(input: T): T {
    console.log(input.name); // 正确
    return input;
}

泛型使用多个类型变量

可以同时使用多个类型变量.

function test<T,U>(a:T,b:U):[T,U]{
    return [a,b];
}

Partial 工具方法

让属性都变成可选的

type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}

Required 工具方法

让属性都变成必选.

type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}

Pick<T,K> 工具方法

只保留自己选择的属性, K代表要保留的属性键值

type A  = Pick<{a:number,b:string,c:boolean}, 'a'|'b'>
type A1 = Pick<A, 'a'|'b'> //  {a:number,b:string}

Omit<T,K> 工具方法

实现排除已选的属性.

type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}

Record<K,T> 工具方法

创建一个类型,K代表键值的类型, T代表值的类型

type A1 = Record<string, string> // 等价{[k:string]:string}

Exclude<T,U> 工具方法

过滤T中和U相同(或兼容)的类型

type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number

// 兼容
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉

Extract<T,U> 工具方法

提取T中和U相同(或兼容)的类型.

type A  = {a:number, b:string}
type A1 = Extract<number|string, string|number[]> // string

NonNullable 工具方法

剔除T中的undefined和null.

type A1 = NonNullable<number|string|null|undefined> // number|string

ReturnType 工具方法

获取T的返回值的类型.

type A1= ReturnType<()=>number> // number

InstanceType 工具方法

返回T的实例类型

ts中类有2种类型, 静态部分的类型和实例的类型, 所以T如果是构造函数类型, 那么InstanceType可以返回他的实例类型:

interface A{
    a:HTMLElement;
}

interface AConstructor{
    new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{
    return new AClass();
}

Parameters

获取函数参数类型, 返回类型为元祖, 元素顺序同参数顺序.

interface A{
    (a:number, b:string):string[];
}

type A1 = Parameters<A> // [number, string]

ConstructorParameters

获取构造函数的参数类型, 和Parameters类似, 只是T这里是构造函数类型.

interface AConstructor{
    new(a:number):string[];
}

type A1 = ConstructorParameters<AConstructor> // [number]

keyof

先看系统预设的Partial的源码.

type Partial<T> = {
    [P in keyof T]?: T[P];
};

这里出线了2个新的关键词"keyof"和"in", "keyof"的作用是取对象的所有键值, 比如:

type A = keyof {a:string,b:number} // "a"|"b"

联合类型in

type A = "a"|"b"
type B = {
    [k in A]: string;
}
// B类型:
{
    a: string;
    b: string;
}

extends和三目运算

之前我们在"**泛型约束"**章节使用了"extends"用来约束类型范围, 这里我们结合三目运算实现类型的条件判断.
语法为:

type A = T extends U ? X : Y;

如果U的范围大于T, 比如U有的字段T中都有, 那么A的值为X, 反之为Y.

type T1 = {a:number,x:number};
type U1 = {a:number,b:number};
type A1 = T1 extends U1 ? number : string; // string

type T2 = {a:number,b:number};
type U2 = {a:number};
type A2 = T2 extends U2 ? number : string; // number

系统预设类型中的"Exclude"也是如此实现:

type Exclude<T, U> = T extends U ? never : T;

这里表示如果过滤掉T中有U中也有的类型:

type T1 = number|string;
type U1 = number|string[];
type A = Exclude<T1,U1> // string

infer (类型推断)

单词本身的意思是"推断", 这里表示在extends条件语句中声明待推断的类型变量. 先看下预设类型Parameters:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

上面声明一个P用来表示...args可能的类型, 如果(...args: infer P)范围大于T, 那么返回...args对应的类型, 也就是函数的参数类型, 反之返回never

注意: 开始的T extends (...args: any) => any用来输入的T必须是函数.

应用 infer

接下来我们利用 infer 来实现"删除元祖类型中第一个元素":

export type Tail<T extends unknown[]> = T extends [a: any, ...args: infer P] ? P : never

type A = Tail<[number,string,null]> // [string,null]

类型断言(as)

有些情况下系统没办法自动推断出正确的类型, 就需要我们自行标记下:

document.body.addEventListener('click', e=>{
    e.target.innerHTML = 'test'; // 报错, 因为e.target的类型是EventTarget, 其上没有innerHTML
    (e.target as HTMLElement).innerHTML = 'test';// 正确
});

因为"addEventListener"方法不仅仅dom元素支持, 所以系统的类型描述中"e.target"不能直接标注成"HTMLElement".

但是上面代码在使用的时候, 我们就知道e.target是dom元素, 所以就需要我们告诉ts:"这是dom", 这就是"类型断言", 语法为**"as",** 还可以用"<>"表示:

document.body.addEventListener('click', e=>{
    (e.target as HTMLElement).innerHTML = 'test';
      // 等价
      (<HTMLElement>e.target).innerHTML = 'test';
});

枚举 (Enum)

枚举是typescript自己的数据类型, javascript中并没有这个类型, 但是这并没有兼容问题, 因为在编译后枚举会被编译成js代码.

使用"enum"关键字定义枚举 , 可以把enum和Class的格式很像, 成员用"="赋值**:**

enum Direction {Up, Right, Down, Left};

看下编译后的js代码:

var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Right"] = 1] = "Right";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
})(Direction || (Direction = {}));

从这里我们可以到作为枚举成员的值和键都被当做对象的键值进行了双向赋值, 实际我们访问的就是这个对象, 所以枚举支持双向访问:

Direction[0] // 'Up'
Direction.Up // 0

Enum值递增

枚举的成员的值如不指定, 那么默认从0递增赋值:

Direction.Up // 0
Direction.Right // 1
Direction.Down // 2
Direction.Left // 3

如果某个成员被赋值了数字, 那么从该数字递增:

enum Direction {Up=5, Right, Down=10, Left};
Direction.Up // 5
Direction.Right // 6
Direction.Down // 10
Direction.Left // 11

Enum值只可以是number和string

// 正确
enum Direction {Up = '上', Right = 1};

// 错误
enum Direction {Up = ['上]', Right = {r:1}};

Enum值也可以是计算值

如果表达式的结果是number或string也是可以作为枚举成员的值的:

function greenValue(){
    return 2;
}

enum Color {Red = 1, Green = greenValue(), Blue='aaa'.length};

但是要注意: 如果某一个成员没有值, 那么他前面的成员必须是字面量的值才可以:

function greenValue(){
    return 2;
}
// 错误, Blue要求Green是个字面量的数字或字符串
enum Color {Red = 1, Green = greenValue(), Blue};

// 没问题, 首个成员前面没有成员, 所以不受上面的限制
enum Color1 {Red, Green = greenValue(), Blue='aaa'.length}; 

常量枚举(const)

使用"const enum"来声明常量枚举,

const enum Enum {
  A = 1,
  B = A * 2,
}

Enum.A
Enum.B

不同于普通的枚举, 常量枚举在编译成js后, 不会生成多余的代码:

// 编译后的结果
"use strict";
1 /* A */;
2 /* B */;

声明文件

如果ts项目中引入了js文件, 那么就需要写"声明文件"来告诉ts引入js文件中的变量类型 , 应用场景

  1. 如果在html中引入了第三方js库, 比如"jq", 那么在ts代码中要使用"$"变量就需要"声明", 因为ts不知道$的存在.

  2. 对全局对象"window/doucument"等上缺少(或自定义)的字段进行补充.

  3. 已存在的npm中的js包, 补充其类型声明.

  4. 对npm上已经存在的ts包的类型进行扩充, 比如向vue实例上增加的$xx属性在ts中都需要自己声明才能使用.

    针对第一种情况实现代码如下:

    // global.d.ts
    declare var $: (selector: string) => any;
    

    这里**"declare"**是声明的关键字, 其他场景实例在后面的课中讲解.

    npm上的声明文件

    像jq这种常见的js库, 其实已经有人写好了声明文件, 并发布到了npm中, 比如jq的声明文件的包是"@types/jquery", lodash的声明文件是"@types/lodash", 如果你通过npm安装的包没有声明文件你可以执行对应的安装命令:

    npm i @types/xx
    

    声明文件一般放在项目根目录下, 这样整个项目的ts文件都可以读取到声明的类型, 命名格式为"xx.d.ts".

    全局interface / type

    上面我们说是给变量增加类型声明, 实际我们还可以在"global.d.ts"中添加接口和type, 添加后接口就会变成全局的, 这样在项目的任意文件中都可以读取到该类型.

    // global.d.ts
    interface Abc{
     n:number
    }
    
    // index.ts
    const abc:Abc = {n:100};
    

    引入(///)

    如果你的想要复用已存在的类型, 我们可以通过"///"语法去引入:

    // test.d.ts
    type ABC = number;
    
    // global.d.ts
    /// <reference path="./test.d.ts" />
    declare const a:ABC // number
    

全局变量的声明

对于全局变量的声明我们使用"declare"关键字实现. 再次强调下声明文件我们一般放在项目的根目录, 为了语义起名叫"global.d.ts".

下面展示各种类型数据的声明写法.

声明全局变量 declare var

// global.d.ts
declare var n:number;
declare let s:string;
declare const version:string;

这里的"var"也可以是"let"或"const", 语义和js中的一致, 根据情况可自行决定.

声明全局函数 declare function

// global.d.ts
declare function translate(words: string): string;

声明全局 枚举 declare enum

// global.d.ts
declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

声明全局类 declare class

// global.d.ts
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}

声明命名空间 declare namespace

声明命名空间, 可以理解为声明一个对象.

// global.d.ts
declare namespace req {
    function get(url: string, params?: Record<string,any>): Promise<any>;
}

扩展全局变量

如果对全局变量进行了扩展, 那么就需要声明扩展后的字段.比如给"String"增加cut方法.

String.prototype.cut = function(n){
    return this.substr(0,n);
};

'12345'.cut(3); // 123

对应的声明文件:

// global.d.ts
interface String {
  // 自定义字段
  cut(s:string): string;
}

因为"String"类型是ts系统自带的声明文件中写好的, 本身就是用interface描述的, 还记得interface类型会自动合并的特性吧, 这里直接写一个同名interface实现类型合并.

系统的声明文件

js的api在ts中都是有类型声明的, 我们可以在项目的"node_modules/typescript/lib/"中看到所有的系统声明文件.

带浏览器前缀的API

我观察发现系统自带的声明, 不会对带"浏览器前缀"的API进行声明, 如果我们自己写插件就需要补充这部分, 比如"requestFullScreen"这个api:

// global.d.ts
interface HTMLElement {
    webkitRequestFullscreen(options?: FullscreenOptions): Promise<void>;
    webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
    msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
    mozRequestFullScreen(options?: FullscreenOptions): Promise<void>;
}

源码: https://github.com/any86/be-full/blob/master/global.d.ts

扩展npm模块的类型(declare module)

npm下载的"包"自带了声明文件, 如果我们需要对其类型声明进行扩展就可以使用"declare module"语法.

让vue3支持this.$axios

// main.ts
app.config.globalProperties.$axios = axios;

功能上我们实现了"this.$axios", 但是ts并不能自动推断出我们添加了$axios字段, 也就是在组件内使用this.$axios会提示缺少类型, 所以添加如下声明文件:

// global.d.ts

import { ComponentCustomProperties } from 'vue'

// axios的实例类型
import { AxiosInstance } from 'axios'

// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module '@vue/runtime-core' {
  
  // 给`this.$http`提供类型
  interface ComponentCustomProperties {
    $axios: AxiosInstance;
  }
}

这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.

上面的例子中我们扩充了原声明中的interface, 但是如果导出是一个Class我们该如何写呢? 下面我们对"any-touch"的类型进行扩充, 这里"any-touch"的默认导出是一个Class. 假设我们对"any-touch"的代码做了如下修改:

  1. 导出增加"aaa"变量, 是string类型.

  2. 类的实例增加"bbb"属性, 是number类型.

  3. 类增加静态属性"ccc", 是个函数.

    // global.d.ts
    
    // AnyTouch一定要导入, 因为只有导入才是扩充, 不导入就会变成覆盖.
    import AnyTouch from 'any-touch'
    
    declare module 'any-touch' {
     // 导出增加"aaa"变量, 是个字符串.
     export const aaa: string;
    
     export default class {
       // 类增加静态属性"ccc", 是个函数.
       static ccc:()=>void
       // 类的实例增加"bbb"属性, 是number类型.
       bbb: number
     }
    }
    

    注意: AnyTouch一定要导入, 因为只有导入才是类型扩充, 不导入就会变成覆盖.

测试下, 类型都已经正确的添加:

// index.ts
import AT,{aaa} from 'any-touch';

const s = aaa.substr(0,1);

const at = new AT();
at.bbb = 123;

AT.ccc = ()=>{};

对非ts/js文件模块进行类型扩充

ts只支持模块的导入导出, 但是有些时候你可能需要引入css/html等文件, 这时候就需要用通配符让ts把他们当做模块, 下面是对".vue"文件的导入支持(来自vue官方):

// global.d.ts
declare module '*.vue' {
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
// App.vue
// 可以识别vue文件
import X1 from './X1.vue';
export default defineComponent({
    components:{X1}
})

声明把vue文件当做模块, 同时标注模块的默认导出是"component"类型. 这样在vue的components字段中注册模块才可以正确识别类型

vuex增加$store属性

下面是vuex官方提供的, 在vue的实例上声明增加$store属性, 有了前面的知识, 看这个应该很轻松.

// vuex.d.ts

import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'

// 声明要扩充@vue/runtime-core包的声明
declare module '@vue/runtime-core' {
  // declare your own store states
  interface State {
    count: number
  }

  // provide typings for `this.$store`
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}