为vue3学点typescript-解读高级类型

47次阅读

共计 9002 个字符,预计需要花费 23 分钟才能阅读完成。

直达

第一课, 体验 typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

回顾

第二课的时候为了更好的讲解 基础类型 , 所以我们讲解了 一部分高级类型 , 比如 ” 接口(interface )” / “ 联合类型(| )” / “ 交叉类型(& )”, 本节课我会把剩余高级类型都讲完.

知识点摘要

本节课主要关键词为: 自动类型推断 / 类型断言 / 类型别名 (type) / 映射类型 (Pick/Record 等 …) / 条件类型 (extends) / 类型推断(infer)

自动类型推断(不用你标类型了,ts 自己猜)

第二课我们讲了那么多基础类型, 大家现在写 ts 的时候一定会在每个变量后面都加上类型吧? 但是?

现在告诉大家 有些情况下你不需要标注类型 , ts 可以根据你写的代码来 自动推断出类型:

赋值字面量给变量

let n = 1; // ts 会自动推断出 n 是 number 类型
n+=3 // 不报错, 因为已知类型

let arr1 = []; // 类型为: never[]
arr1.push(1); // 报错,

let arr = [1]; // 内部要有数字, 才能推断出正确类型
arr.push(2);
自动阅读 if 条件判断
let n: number|null = 0.5 < Math.random() ? 1:null;
if(null !== n){n+=3 // ts 知道现在 n 不是 null 是 number}
浏览器自带 api
document.ontouchstart = ev=>{ // 能自动推断出 ev 为 TouchEvent
    console.log(ev.touches);  // 不报错, TouchEvent 上有 touches 属性
}
typeof

typeof就是 js 中的 typeof, ts 会根据你代码中出现的typeof 来自动推断类型:

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

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

注意 : 在 ts 文档中, 该部分的知识点叫做typeof 类型保护, 和其他类型推断的内容是分开的, 被写在 高级类型 / 类型保护 章节中.

instanceof

ts 会根据你代码中出现的 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);
}

注意 : 在 ts 文档中, 该部分的知识点叫做instanceof 类型保护, 和其他类型推断的内容是分开的, 被写在 高级类型 / 类型保护 章节中.

类型断言(你告诉 ts 是什么类型, 他都信)

有些情况下系统没办法自动推断出正确的类型, 就需要我们标记下, 断言有 2 种语法, 一种是通过 ”<>“, 一种通过 ”as“, 举例说明:

let obj = 0.5 < Math.random() ? 1 : [1]; // number|number[]

// 断言, 告诉 ts, obj 为数组
(<number[]>obj).push(1);

// 等价
(obj as number[]).push(1);

类型别名(type)

类型别名可以表示很多接口表示不了的类型, 比如字面量类型(常用来校验取值范围):

type A = 'top'|'right'|'bottom'|'left'; // 表示值可能是其中的任意一个
type B = 1|2|3;
type C = '红'|'绿'|'黄';
type D = 150;

let a:A = 'none'; // 错误, A 类型中没有 'none'
更多组合:
interface A1{a:number;}
type B = A1 | {b:string};
type C = A1 & {b:string};

// 与泛型组合
type D<T> = A1 | T[];

索引类型(keyof)

js 中的 Object.keys 大家肯定都用过, 获取对象的键值 , ts 中的keyof 和他类似, 可以用来获取对象类型的键值:

type A = keyof {a:1,b:'123'} // 'a'|'b'
type B = keyof [1,2] // '1'|'2'|'push'... , 获取到内容的同时, 还得到了 Array 原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)

可以获得键值, 也可以获取对象类型的值的类型:

type C = A['a'] // 等于 type C = 1;
let c:C = 2 // 错误, 值只能是 1 

映射类型(Readonly, Pick, Record 等 …)

映射类型比较像修改类型的工具函数, 比如 Readonly 可以把每个属性都变成只读:

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

打开 node_modules/typescript/lib 文件夹可以找到 lib.es5.d.ts, 在这我们能找到Readonly 的定义:

type Readonly<T> = {readonly [P in keyof T]: T[P];
};

其实不是很复杂, 看了本节课前面前面的内容, 这个很好理解是吧:

  1. 定义一个支持泛型的类型别名, 传入类型参数T.
  2. 通过 keyof 获取 T 上的键值集合.
  3. in 表示循环 keyof 获取的键值.
  4. 添加 readonly 标记.
Partial<T>, 让属性都变成可选的
type A  = {a:number, b:string}
type A1 = Partial<A> // {a?: number; b?: string;}
Required<T>, 让属性都变成必选
type A  = {a?:number, b?:string}
type A1 = Required<A> // {a: number; b: string;}
Pick<T,K>, 只保留自己选择的属性, U 代表属性集合
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}
Omit<T,K> 实现排除已选的属性
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
Record<K,T>, 创建一个类型,T 代表键值的类型, U 代表值的类型
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>, 剔除 T 中的 undefined 和 null
type A1 = NonNullable<number|string|null|undefined> // number|string
ReturnType<T>, 获取 T 的返回值的类型
type A1= ReturnType<()=>number> // number
InstanceType<T>, 返回 T 的实例类型

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

interface A{a:HTMLElement;}

interface AConstructor{new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{return new AClass();
}
Parameters<T> 获取函数参数类型

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

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

type A1 = Parameters<A> // [number, string]
ConstructorParameters<T> 获取构造函数的参数类型

Parameters 类似, 只是 T 这里是构造函数类型.

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

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

extends(条件类型)

T extends U ? X : Y

用来表示类型是不确定的, 如果 U 的类型可以表示T, 那么返回X, 否则Y. 举几个例子:

type A =  string extends '123' ? string :'123' // '123'
type B =  '123' extends string ? string :123 // string

明显 string 的范围更大, '123'可以被 string 表示, 反之不可.

infer(类型推断)

单词本身的意思是 ” 推断 ”, 实际表示在 extends 条件语句中 声明 待推断的类型变量. 我们上面介绍的 映射类型 中就有很多都是 ts 在 lib.d.ts 中实现的, 比如Parameters:

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

为 vue3 学点 typescript, 解读高级类型

直达

第一课, 体验 typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

回顾

第二课的时候为了更好的讲解 基础类型 , 所以我们讲解了 一部分高级类型 , 比如 ” 接口(interface )” / “ 联合类型(| )” / “ 交叉类型(& )”, 本节课我会把剩余高级类型都讲完.

知识点摘要

本节课主要关键词为: 自动类型推断 / 类型断言 / 类型别名 (type) / 映射类型 (Pick/Record 等 …) / 条件类型 (extends) / 类型推断(infer)

自动类型推断(不用你标类型了,ts 自己猜)

第二课我们讲了那么多基础类型, 大家现在写 ts 的时候一定会在每个变量后面都加上类型吧? 但是?

现在告诉大家 有些情况下你不需要标注类型 , ts 可以根据你写的代码来 自动推断出类型:

赋值字面量给变量

let n = 1; // ts 会自动推断出 n 是 number 类型
n+=3 // 不报错, 因为已知类型

let arr1 = []; // 类型为: never[]
arr1.push(1); // 报错,

let arr = [1]; // 内部要有数字, 才能推断出正确类型
arr.push(2);
自动阅读 if 条件判断
let n: number|null = 0.5 < Math.random() ? 1:null;
if(null !== n){n+=3 // ts 知道现在 n 不是 null 是 number}
浏览器自带 api
document.ontouchstart = ev=>{ // 能自动推断出 ev 为 TouchEvent
    console.log(ev.touches);  // 不报错, TouchEvent 上有 touches 属性
}
typeof

typeof就是 js 中的 typeof, ts 会根据你代码中出现的typeof 来自动推断类型:

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

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

注意 : 在 ts 文档中, 该部分的知识点叫做typeof 类型保护, 和其他类型推断的内容是分开的, 被写在 高级类型 / 类型保护 章节中.

instanceof

ts 会根据你代码中出现的 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);
}

注意 : 在 ts 文档中, 该部分的知识点叫做instanceof 类型保护, 和其他类型推断的内容是分开的, 被写在 高级类型 / 类型保护 章节中.

类型断言(你告诉 ts 是什么类型, 他都信)

有些情况下系统没办法自动推断出正确的类型, 就需要我们标记下, 断言有 2 种语法, 一种是通过 ”<>“, 一种通过 ”as“, 举例说明:

let obj = 0.5 < Math.random() ? 1 : [1]; // number|number[]

// 断言, 告诉 ts, obj 为数组
(<number[]>obj).push(1);

// 等价
(obj as number[]).push(1);

类型别名(type)

类型别名可以表示很多接口表示不了的类型, 比如字面量类型(常用来校验取值范围):

type A = 'top'|'right'|'bottom'|'left'; // 表示值可能是其中的任意一个
type B = 1|2|3;
type C = '红'|'绿'|'黄';
type D = 150;

let a:A = 'none'; // 错误, A 类型中没有 'none'
更多组合:
interface A1{a:number;}
type B = A1 | {b:string};
type C = A1 & {b:string};

// 与泛型组合
type D<T> = A1 | T[];

索引类型(keyof)

js 中的 Object.keys 大家肯定都用过, 获取对象的键值 , ts 中的keyof 和他类似, 可以用来获取对象类型的键值:

type A = keyof {a:1,b:'123'} // 'a'|'b'
type B = keyof [1,2] // '1'|'2'|'push'... , 获取到内容的同时, 还得到了 Array 原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)

可以获得键值, 也可以获取对象类型的值的类型:

type C = A['a'] // 等于 type C = 1;
let c:C = 2 // 错误, 值只能是 1 

映射类型(Readonly, Pick, Record 等 …)

映射类型比较像修改类型的工具函数, 比如 Readonly 可以把每个属性都变成只读:

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

打开 node_modules/typescript/lib 文件夹可以找到 lib.es5.d.ts, 在这我们能找到Readonly 的定义:

type Readonly<T> = {readonly [P in keyof T]: T[P];
};

其实不是很复杂, 看了本节课前面前面的内容, 这个很好理解是吧:

  1. 定义一个支持泛型的类型别名, 传入类型参数T.
  2. 通过 keyof 获取 T 上的键值集合.
  3. in 表示循环 keyof 获取的键值.
  4. 添加 readonly 标记.
Partial<T>, 让属性都变成可选的
type A  = {a:number, b:string}
type A1 = Partial<A> // {a?: number; b?: string;}
Required<T>, 让属性都变成必选
type A  = {a?:number, b?:string}
type A1 = Required<A> // {a: number; b: string;}
Pick<T,K>, 只保留自己选择的属性, U 代表属性集合
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}
Omit<T,K> 实现排除已选的属性
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
Record<K,T>, 创建一个类型,T 代表键值的类型, U 代表值的类型
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>, 剔除 T 中的 undefined 和 null
type A1 = NonNullable<number|string|null|undefined> // number|string
ReturnType<T>, 获取 T 的返回值的类型
type A1= ReturnType<()=>number> // number
InstanceType<T>, 返回 T 的实例类型

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

interface A{a:HTMLElement;}

interface AConstructor{new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{return new AClass();
}
Parameters<T> 获取函数参数类型

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

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

type A1 = Parameters<A> // [number, string]
ConstructorParameters<T> 获取构造函数的参数类型

Parameters 类似, 只是 T 这里是构造函数类型.

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

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

extends(条件类型)

T extends U ? X : Y

用来表示类型是不确定的, 如果 U 的类型可以表示T, 那么返回X, 否则Y. 举几个例子:

type A =  string extends '123' ? string :'123' // '123'
type B =  '123' extends string ? string :123 // string

明显 string 的范围更大, '123'可以被 string 表示, 反之不可.

infer(类型推断)

单词本身的意思是 ” 推断 ”, 实际表示在 extends 条件语句中 声明 待推断的类型变量. 我们上面介绍的 映射类型 中就有很多都是 ts 在 lib.d.ts 中实现的, 比如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 是否是函数, 如果不是 ts 会报错, 如果直接替换成 T 不会有报错, 会一直返回never.

应用 infer

接下来我们利用 infer 来实现 ” 删除元祖类型中第一个元素 ”, 这常用于简化函数参数, 这有一个我之前的应用

export type Tail<Tuple extends any[]> = ((...args: Tuple) => void) extends ((a: any, ...args: infer T) => void) ? T : never;

总结

多写多练, 很快就上手, 放几个我用 ts 写的项目当做参考, 抛砖引玉, 加油!

手势库: https://github.com/any86/any-…

命令式调用 vue 组件: https://github.com/any86/vue-…

工作中常用的一些代码片段: https://github.com/any86/usef…

一个 mini 的事件管理器: https://github.com/any86/any-…
上面声明一个 P 用来表示 ...args 可能的类型, 如果 (...args: infer P) 可以 表示 T, 那么返回...args 对应的类型, 也就是函数的参数类型, 反之返回never.

注意: 开始的 T extends (...args: any) => any 用来校验输入的 T 是否是函数, 如果不是 ts 会报错, 如果直接替换成 T 不会有报错, 会一直返回never.

应用 infer

接下来我们利用 infer 来实现 ” 删除元祖类型中第一个元素 ”, 这常用于简化函数参数, 这有一个我之前的应用

export type Tail<Tuple extends any[]> = ((...args: Tuple) => void) extends ((a: any, ...args: infer T) => void) ? T : never;

总结

多写多练, 很快就上手, 放几个我用 ts 写的项目当做参考, 抛砖引玉, 加油!

手势库: https://github.com/any86/any-…

命令式调用 vue 组件: https://github.com/any86/vue-…

工作中常用的一些代码片段: https://github.com/any86/usef…

一个 mini 的事件管理器: https://github.com/any86/any-…

正文完
 0