TypeScript笔记
💡专业能力要求:
P5/P4: 常见简单的会用,能遵守上述规范,常见系统内置类型会用, 如| & ? typeof, Pick<>
P6: 泛型、递归类型、索引类型、系统内置类型会灵活使用
P7: 系统内置类型能实现, 包括但不限于 extends/infer/ThisType<>/InstanceOf/Exclude; 能实现开源类库d.ts 同等难度。
在线Demo
add 2023年8月11日
阮一峰老师新出的TypeScript教程,强烈推荐!
TypeScript 教程 https://wangdoc.com/typescript/
TypeScript 是静态类型
类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。
动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型。
静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型
TypeScript 是弱类型
类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型。
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以它们都是弱类型。
这样的类型系统体现了 TypeScript 的核心设计理念:在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。
TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。
TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。
所以,TypeScript通过类型注解提供编译时的静态类型检查。
可以通过配置tsconfig.json
,在报错的时候终止 js 文件的生成,配置 noEmitOnError
即可。
可通过tsc命令来查看ts文件编译后的js文件
安装typescript
npm install -g typescript
执行tsc命令编译ts文件
tsc filename.ts
原始数据类型
基本数据类型
string
boolean
number
bigint
null
undefined
let unusable: void = undefined;
let u: undefined = undefined;
let n: null = null;
与 void
的区别是,undefined
和 null
是所有类型的子类型。也就是说 undefined
类型的变量,可以赋值给 number
类型的变量,但是void
类型的不可以:
let num: number = undefined;
PS:以上只在**
"strict": true,
非严格模式下生效**
enum
使用枚举我们可以定义一些带名字的常量。TypeScript支持数字的和基于字符串的枚举。
数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
如上,我们定义了一个数字枚举, Up
使用初始化为 1
。 其余的成员会从 1
开始自动增长。 换句话说, Direction.Up
的值为 1
, Down
为 2
, Left
为 3
, Right
为 4
。
我们还可以完全不使用初始化器:
enum Color {
red, // 默认为0
green,
blue,
}
字符串枚举
字符串枚举的概念很简单,但是有细微的 运行时的差别。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const常量枚举
大多数情况下,枚举是十分有效的方案。 然而在某些情况下为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const
枚举。
一般的枚举。在运行执行时,它将会查找变量 Color.Blue
enum Color {
Red,
Blue,
Green
}
let color = Color.Blue
// 会被编译成
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Blue"] = 1] = "Blue";
Color[Color["Green"] = 2] = "Green";
})(Color || (Color = {}));
var color = Color.Blue;
如果是常量枚举,则会在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。
const enum Color {
Red,
Blue,
Green
}
let color = Color.Blue
// 会被编译成
var color = 1 /* Blue */;
any
1、声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
2、变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
any类型完全不被类型检查
当你不想写出一个长的类型来说服TypeScript相信某一行代码是可行的时候,any类型是有用的。
let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
unknown
https://juejin.cn/post/7024686544086630436
https://juejin.cn/post/6844903866073350151
TypeScript 3.0 引入了新的unknown
类型,这与any类型类似,但更安全,因为对未知值做任何事情都是不合法的。
unknown
和 any
的主要区别是 unknown
类型会更加严格:在对 unknown
类型的值执行大多数操作之前,我们必须进行某种形式的检查 (意味着在我们已经知道内容类型)。而在对any
类型的值执行操作之前,我们不必进行任何检查。
function foo(a: unknown) {
a.split('') // Object is of type 'unknown'
}
function foo(a: unknown) {
if (typeof a === 'string') {
a.split('')
}
}
object
特殊类型的object指的是任何不是基元的值(字符串、数字、bigint、布尔值、符号、null或未定义)。这与空对象类型{ }不同,也与全局类型Object不同。你很可能永远不会使用Object。
object类型的关注点在于类型的分类,它强调一个类型是非原始类型,即对象类型。
object
is notObject
. Always useobject
!
Object是Object.prototype的类型。
never
- 一个从来不会有返回值的函数(如:如果函数内含有
while(true) {}
); - 一个总是会抛出错误的函数(如:
function foo() { throw new Error('Not Implemented') }
,foo
的返回类型是never
);
function bar() : never {
throw new Error('xxx');
}
function bar() : never {
while(1) {}
}
与 void
的差异
实际上,void
表示没有任何类型,never
表示永远不存在的值的类型。
当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never 类型。void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。
联合类型
let something: string | number;
访问联合类型的属性或方法:只能访问此联合类型的所有类型里共有的属性或方法:
function getString(something: string | number): string {
return something.toString();
}
否则会报错:
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
//Property 'toUpperCase' does not exist on type 'number'.
}
解决方案是用代码缩小联合,就像你在没有类型注释的JavaScript中一样。当TypeScript可以根据代码的结构为一个值推断出一个更具体的类型时,就会发生缩小。
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
联合类型的一个流行的用例是描述一个值所允许的字符串或数字字面的集合。
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumbersUnderTen = 1 | 3 | 5 | 7 | 9;
对象类型
接口interface
匿名的方式:
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
或者可以通过使用接口的方式:
interface Person {
name: string;
age: number;
}
function greet(person: Person) {
return "Hello " + person.name;
}
赋值的时候,变量的形状必须和接口的形状保持一致,少了一些属性,多一些属性都是不允许的。
let tom: Person = {
name: 'Tom',
age: 25
};
可选属性 ?:
interface Person {
name: string;
age?: number;
}
任意属性(索引签名)
有时你不能提前知道一个类型的所有属性名称,但你知道这些值的形状。
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number; // age不是string类型会报错
[x: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number;
[x: string]: string | number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
**在该语法中, x表示索引名, 它可以为任意合法的标识符。索引名只起到占位的作用, 它不代表真实的对象属性名; **
在字符串索引签名中, 索引名的类型必须为string或者number类型;
只读属性readonly
只能在创建的时候被赋值。
虽然它不会在运行时改变任何行为,但在类型检查期间,标记为只读的属性不能被写入。
如果修改只读属性,则会报错。
interface Person {
readonly name: string;
}
扩展类型extends
接口上的extends关键字允许我们有效地从其他命名的类型中复制成员,并添加我们想要的任何新成员。
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
interface AddressWithUnit extends BasicAddress {
unit: string;
}
也可以从多个类型中扩展:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
interface ColorfulCircle extends Colorful, Circle {}
const cc: ColorfulCircle = {
color: "red",
radius: 42,
};
交叉类型 &
TypeScript提供了另一个叫做交叉类型的结构,主要用于组合现有的对象类型。
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = Colorful & Circle;
声明合并
interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
相当于
interface Alarm {
price: number;
weight: number;
}
数组类型
1、「类型 + 方括号」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5];
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
2、数组泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
3、用接口表示数组(一般用来表示伪数组)
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
// 函数参数arguments
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
元组
元组类型是另一种数组类型,它确切地知道它包含多少个元素,以及它在特定位置包含哪些类型。
type StringNumberPair = [string, number];
//使用
let p:StringNumberPair = ['1', 1]
只读元组
//方式一
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!"; // Cannot assign to '0' because it is a read-only property.
}
//方式二
const red: ReadonlyArray<number> = [255, 0, 0];
//方式三
const red: Readonly<number[]> = [255, 0, 0];
const断言的数组字面将被推断为只读元组类型。
let point = [3, 4] as const;
函数类型
函数声明
function sum(x: number, y: number): number {
return x + y;
}
函数表达式
// 错误写法
let mySum = function (x: number, y: number): number {
return x + y;
};
// 正确写法
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
注意不要混淆了 TypeScript 中的 =>
和 ES6 中的 =>
。
在 TypeScript 的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
函数定义
字面量
type foo = () => void
用接口定义函数的形状(调用签名)
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
可选参数
可选参数必须接在必需参数后
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
参数默认值
TypeScript 会将添加了默认值的参数识别为可选参数,且不受「可选参数必须接在必需参数后面」的限制
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
剩余参数
function push(array: number[], ...items: number[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
//元组类型的剩余参数
function f(...args: [boolean, number]) {}
常规元组类型,示例如下:
function f0(...args: [boolean, number]) {}
// 等同于:function f1(args_0: boolean, args_1: number) {}
带有可选元素的元组类型,示例如下:
function f0(...args: [boolean, string?]) {}
// 等同于:function f1(args_0: boolean, args_1?: string) {}
带有剩余元素的元组类型,示例如下:
function f0(...args: [boolean, ...string[]]) {}
// 等同于 function f1(args_0: boolean, ...args_1: string[]) {}
解构参数
function f0([x, y]: [number, number]) {}
f0([0, 1]);
function f1({ x, y }: { x: number; y: number }) {}
f1({ x: 0, y: 1 });
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
一些函数被设计为既可以作为普通函数使用,同时又可以作为构造函数来使用。例如,JavaScript内置的“Number()”函数和“String()”函数等都属于这类函数。
declare const F: {
new (x: number): Number; // <- 构造签名
(x: number): number; // <- 调用签名
};
// 作为普通函数调用
const a: number = F(1);
// 作为构造函数调用
const b: Number = new F(1);
函数中this类型
在默认情况下, 编译器会将函数中的this值设置为any类型, 并允许程序在this值上执行任意的操作。
将this值的类型设置为any类型对类型检查没有任何帮助。因此,TypeScript提供了一个“--noImplicitThis
”编译选项。当启用了该编译选项时,如果this值默认获得了any类型,那么将产生编译错误;如果函数体中没有引用this值,则没有任何影响。
TypeScript支持在函数形式参数列表中定义一个特殊的this参数来描述该函数中this值的类型。
this参数固定使用this作为参数名。this参数是一个可选的参数,若存在,则必须作为函数形式参数列表中的第一个参数。this参数的类型即为函数体中this值的类型。
function foo(this: { name: string }) {
this.name = 'Patrick';
}
类型断言
手动指定一个值的类型。像类型注解一样,类型断言会被编译器删除,不会影响你的代码的运行行为。
语法:
值 as 类型
类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误。
用途:
- 将一个联合类型断言为其中一个类型
- 将一个父类断言为更加具体的子类
- 将任何一个类型断言为 any
- 将 any 断言为一个具体的类型
类型断言的限制
A和B需要相互兼容。也可以理解成A继承B,或者B继承A,就是相互兼容。
双重断言
不要使用。
非空断言!
TypeScript也有一个特殊的语法,用于从一个类型中移除null和undefined,而不做任何明确的检查。在任何表达式后面写上 !
是一个有效的类型断言,该值不是空的或未定义的。
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
liveDangerously() // TypeError: Cannot read property 'toFixed' of undefined
x!
将从 x
值对应的类型集合中中排除 null
和 undefined
的类型。比如 x 可能是 number | undefind
,则 x!
类型缩窄为 number
。
注意:就像其他类型断言一样,这不会改变你的代码的运行行为,所以重要的是,只有当你知道值不可能是空的或未定义的时候才使用!
。
声明文件💡
声明语法
- declare var 声明全局变量
- declare function 声明全局方法
- declare class 声明全局类
- declare enum 声明全局枚举类型
- declare namespace 声明(含有子属性的)全局对象
- interface 和 type 声明全局类型
- export 导出变量
- export namespace 导出(含有子属性的)对象
- export default ES6 默认导出
- export = commonjs 导出模块
- export as namespace UMD 库声明全局变量
- declare global 扩展全局变量
- declare module 扩展模块
- /// <reference /> 三斜线指令
第三方库不能直接使用全局变量,需要声明变量的类型,如jQuery
declare var jQuery: (selector: string) => any;
声明文件
以 .d.ts
为后缀。
引入第三方声明文件
当然,jQuery 的声明文件不需要我们定义了,社区已经帮我们定义好了:jQuery in DefinitelyTyped。
我们可以直接下载下来使用,但是更推荐的是使用 @types
统一管理第三方库的声明文件。
@types
的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
可以在这个页面搜索你需要的声明文件。
手动书写声明文件
泛型
https://segmentfault.com/q/1010000005712897/a-1020000005713059
泛型(Generics)是指在定义函数
、接口
或类
的时候,不预先指定具体的类型,而在使用的时候再指定(或者自动推断)类型的一种特性。
首先我们看下面的代码,我们定一个 idientity0 函数,这个函数逻辑非常简单,就是直接返回参数,那么我们怎么确定返回值的类型呢?
因为输入值可以是任意属性,所以我们只能写出 identity0 这个函数,参数和返回值类型都是 any,但是明显不能满足我们的需求。我们需要返回值的类型和参数一致,所以我们在函数名之后使用 <> 定一个泛型 T,你可以理解这个 T 的意思就是给函数参数定义了一个类型变量,会在后面使用,相当于【type T = arg 的类型】,返回值使用 T 这个类型就完成了这个需求。
**<T>
**让我们拥有了给函数的参数定义类型变量的能力。
function identity0(arg: any): any {
return arg
}
// 相当于type T = arg的类型
function identity<T>(arg: T): T {
return arg
}
identity<string>('玩转vue 3全家桶') // 这个T就是string,所以返回值必须得是string
identity<number>(1)
泛型函数
有些函数的参数,你在定义的时候是不确定的类型,而返回值类型需要根据参数来确定。
函数参数括号前添加了<T>
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array<T>
中即可使用了。
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
泛型类型字面量
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
泛型接口
// 定义泛型接口
interface Identity<T> {
attr: T;
}
// 用真实类型替换泛型接口的类型变量
// Identity<number>作为一个整体相当于一个接口名
let a: Identity<number> = {attr: 10};
// Identity<string>作为一个整体相当于一个接口名
let b: Identity<string> = {attr: 'hello'};
// 错误,类型不匹配,数字10是数字类型,而类型变量为布尔类型
// error TS2322: Type 'number' is not assignable to type 'boolean'.
let c: Identity<boolean> = {attr: 10};
// 一个复杂点的例子
function fn(){};
let c: Identity<typeof fn> = {
attr(){}
}
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 相关属性的变量。这就是泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
泛型默认值
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
type别名
type为类型别名。
当想要多次使用同一个类型并以一个名字来指代它的时候。
type Point = {
x: number;
y: number;
};
type ID = number | string;
interface vs type
递归类型别名
一般情况下,在类型别名声明中赋值运算符右侧的类型不允许引用当前定义的类型别名。因为类型别名对其引用的类型使用的是及早求值的策略,而不是惰性求值的策略。因此,如果类型别名引用了自身,那么在解析类型别名时就会出现无限递归引用的问题。示例如下:
type T = T; // 编译错误!类型别名 'T' 存在循环的自身引用
在TypeScript 3.7版本中, 编译器对类型别名的解析进行了一些优化。在类型别名所引用的类型中,使用惰性求值的策略来解析泛型类型参数。因此,允许在泛型类型参数中递归地使用类型别名。总结起来,目前允许在以下场景中使用递归的类型别名:
1)若类型别名引用的类型为接口类型、对象类型字面量、函数类型字面量和构造函数类型字面量,则允许递归引用类型别名。示例如下:
type T0 = { name: T0 };
type T1 = () => T1;
type T2 = new () => T2;
2)若类型别名引用的是数组类型或元组类型,则允许在元素类型中递归地引用类型别名。示例如 下:
type T0 = Array<T0>;
type T1 = T1[];
type T3 = [number, T3];
3)若类型别名引用的是泛型类或泛型接口,则允许在类型参数中递归的引用类型别名。关于泛型的 详细介绍请参考6.1节。示例如下
interface A<T> {
name: T;
}
type T0 = A<T0>;
class B<T> {
name: T | undefined;
}
type T1 = B<T1>;
示例:
const recursiveData = [
{
name: 'First',
children: [
{
name: 'Second',
children: [
{
name: 'Third',
children: [
{
name: 'Fourth',
children: [
{
name: 'Fifth',
children: [
// On and on...
],
},
],
},
],
},
],
},
],
},
];
我们可以定义如下:
type RecursiveObject = {
name: string;
children?: RecursiveObject[];
};
type RecursiveObjects = RecursiveObject[];
或者使用interface代替:
interface RecursiveObject {
name: string;
children?: RecursiveObject[];
}
// and reference it as
const recursiveData: RecursiveObject[];
联合类型(|)与交叉类型(&)
字面量:联合类型由两个或两个以上的成员类型构成,各成员类型之间使用竖线符号“|”分隔。
type NumericType = number | bigint;
或者
interface Circle {}
interface Rectangle{}
type Shape = Circle | Rectangle; // 表示 Shape 是Circle类型,或者是 Rectangle类型
交叉类型
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = Colorful & Circle; //Shape 既是Colorful 类型,又是Circle类型(也就是类型合并了起来)
// 相当于
interface XXX{
color: string;
radius: number;
}
索引类型
索引类型查询keyof
通过索引类型查询能够获取给定类型中的属性名类型。
索引类型查询的结果是由字符串字面量类型构成的联合类型,该联合类型中的每个字符串字面量类型都表示一个属性名类型。
索引类型查询的语法如下所示:
keyof Type
keyof运算符接收一个对象类型,并产生一个其键值的字符串或数字字面联合。下面的类型P是与 "x"|"y "相同的类型。
type Point = {
x: number;
y: number
};
type P = keyof Point; // "x" | y"
索引访问类型
索引访问类型能够获取对象类型中属性成员的类型,它的语法如下所示:
T[K]
在该语法中,T和K都表示类型, 并且要求K类型必须能够赋值给“key of T”类型。“T[K]”的结果类型为T中K属性的类型。
例如,有以下对象类型T:
type T={a:boolean; b:string}
通过索引访问类型能够获取对象类型T中属性x和y的类型,示例如下:
type T={x:boolean; y:string}
type Kx='x';
type TO=T[Kx] ; //boolean
type Ky='y'
type T1=T[Ky] ; //string
应用案例:
通过结合使用索引类型查询和索引访问类型就能够实现类型安全的对象属性访问操作。例如, 下例中定义了工具函数getProperty, 它能够返回对象的某个属性值。
该工具函数的特殊之处在于它还能够准确地返回对象属性的类型。示例如下:
function getProperty<T, K extends keyof T>( obj: T, key: K ): T[K] {
return obj[key];
}
interface Circle {
kind: 'circle';
radius: number;
}
function f(circle: Circle) {
// 正确,能够推断出 radius 的类型为 'circle' 类型
const kind = getProperty(circle, 'kind');
// 正确,能够推断出 radius 的类型为 number 类型
const radius = getProperty(circle, 'radius');
// 错误
const unknown = getProperty(circle, 'unknown');
// ~~~~~~~~~
// 编译错误:'unknown'类型不能赋值给'kind' |'radius'
}
关键字
extends
extends 相当于 TypeScript 世界中的条件语句。
// 类型 ExtendsType,接受泛型参数 T 后,通过判断 T 是不是布尔值来返回不同的类型字符串
type ExtendsType = T extends boolean ? "number" : "string | void"
in
in 关键字可以理解为 TypeScript 世界中的遍历。
// 下面的代码中我们通过 k in Courses 语法,相当于遍历了 Courses 所有的类型作为 CourseObj 的属性,值的类型是 number。
type Courses = '玩转Vue 3'|'重学前端'
type CourseObj = {
[k in Courses]:number // 遍历Courses类型作为key
}
// 上面的代码等于下面的定义
// type CourseObj = {
// 玩转Vue 3: number;
// 重学前端: number;
// }
infer
<T>
让我们拥有了给函数的参数定义类型变量的能力,infer
可以在extends
的条件语句中推断待推断的类型。
// 示例1:使用infer来推断函数的返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
在这个例子中,infer R
代表待推断的返回值类型,如果T
是一个函数,则返回函数的返回值,否则返回any
type func = () => number;
type variable = string;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
type varReturnType = ReturnType<variable>; // varReturnType 类型为 any
// 示例2:infer P 表示待推断的函数参数
type ParamType<T> = T extends (arg: infer P) => any ? P : T;
整句表示为:如果 T
能赋值给 (arg: infer P) => any
,则结果是 (arg: infer P) => any
类型中的参数 P
,否则返回为 T
。
interface User {
name: string;
age: number;
}
type Func = (user: User) => void;
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
参考文档:https://jkchao.github.io/typescript-book-chinese/tips/infer.html#介绍
内置工具类型
Partial<Type>
构建一个类型,将 Type 的所有属性设置为可选类型。
interface Todo {
title: string;
description: string;
}
let update: Partial<Todo> = { title: 'xxx' }
Partial的实现
// 使⽤泛型和映射类型keyof实现Partial
type Partial<T> = {
[P in keyof T]?: T[P];
}
Required<Type>
构建一个由 Type 的所有属性设置为必填的类型。与Partial相反。
Required的实现
type Required<T> = {
[P in keyof T]-?: T[P]
}
-?
的功能就是把可选属性的 ? 去掉使该属性变成必选项,对应的还有 +?
,作⽤与 -? 相反,是把
属性变为可选项。
Readonly<Type>
构造一个类型,Type 的所有属性被设置为只读,这意味着构造的类型的属性不能被重新分配。
Exclude
从T中筛选出那些不可以赋值给 U 的类型
type Exclude<T, U> = T extends U ? never : T
// type T = Exclude<1 | 2 | 3 | 4 | 5, 3 | 4> // T = 1 | 2 | 5
Extract
从 T 中提取那些可以赋值给 U 的类型
type Extract<T, U> = T extends U ? T : never;
Pick<Type, Keys>
通过从Type中选取属性集合Keys(字符串字头或字符串字头的联合)来构造一个类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Pick和Extract的区别:Extract是从字⾯量类型中挑选类型;Pick是从类型的属性中挑选属性。
Record<Keys, Type>
构造一个对象类型,Keys 表示对象的属性键 、Type 表示对象的属性值,用于将一种类型属性映射到另一种类型。
将 K 中所有的属性的值转化为T类型
type Record<K extends keyof any, T> = {
[P in K]: T
}
示例:将'tester' | 'developer' | 'manager' 属性转化成number类型
type roles = 'tester' | 'developer' | 'manager'
const staffCount: Record<roles, number> = {
tester: 10,
developer: 20,
manager: 1
}
Record类型 和 keyof 组合
通过使用keyof
从现有类型中获取所有的属性,并和字符串组合,我们可以做如下事情:
interface Staff {
name:string,
salary:number,
}
type StaffJson = Record<keyof Staff, string>
const product: StaffJson = {
name: 'John',
salary:'3000'
}
InstanceType<Type>
构造一个由 Type 中构造函数的实例类型组成的类型。
class C {
x = 0;
y = 0;
}
// type T0 = C
type T0 = InstanceType<typeof C>;
// type T1 = any
type T1 = InstanceType<any>;
// type T2 = never
type T2 = InstanceType<never>;
Vue中使用:
(this.$refs.pullRefreshList as InstanceType<typeof WsList>).reset();
Vue.extend({})
返回的是constructor 实例, 需要通过 typeof 转为 constructor type, 然后通过InstanceType<> 获取实例类型。
ThisType<Type>
这个工具并不返回一个转换后的类型。相反,它是一个上下文this类型的标记。注意,必须启用noImplicitThis:true;
标志才能使用这个工具。
在下面的例子中,makeObject的参数中的方法对象有一个包括ThisType<D & M>的上下文类型,因此方法对象中this的类型是{ x: number, y: number } & { moveBy(dx: number, dy: number): number }。注意方法属性的类型如何同时是推理目标和方法中this类型的来源。
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // Strongly typed this
this.y += dy; // Strongly typed this
},
},
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
导出导入
使用import导入
使用export导出。
import所有语法
import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name");//这是一个处于第三阶段的提案。
导入单个接口
import {myExport} from '/modules/my-module.js';
导入多个接口
import {foo, bar} from '/modules/my-module.js';
导入带有别名的接口
import {reallyReallyLongModuleExportName as sn} from '/modules/my-module.js';
导入所有接口
import * as myModule from '/modules/my-module.js';
导入默认值
import myDefault from '/modules/my-module.js';
也可以同时将default
语法与上述用法(命名空间导入或命名导入)一起使用。在这种情况下,default
导入必须首先声明。 例如:
import myDefault, * as myModule from '/modules/my-module.js';
// 或者
import myDefault, {foo, bar} from '/modules/my-module.js';
仅为副作用而导入一个模块
整个模块仅为副作用而导入,而不导入模块中的任何内容(接口)。 这将运行模块中的全局代码, 但实际上不导入任何值。
import '/modules/my-module.js';
QA
✅
<script lang='ts'>
✅
export default Vue.extend({ })
✅import vue文件的时候不能省略
.vue
后缀。✅data as 形式 还是属性单个写?
💡单个写好
✅函数没有返回值,写void
typescriptcurrentChange(v:number) : void { this.pageInfo.page = v; this.getList(); },
💡返回值写void
✅mapActions 不用处理
💡mapActions的不用写
✅解构赋值
let {a, b}: {a: string, b: number} = o;
typescriptconst { departmentList, userInfoList } = res.data;
💡
let {a, b}: {a: string, b: number} = o;
是对的✅
async deleteStore(id:string) : Promise<any>
泛型:https://segmentfault.com/q/1010000005712897/a-1020000005713059
✅
object → Record<string, unknown>
✅断言可以这样写吗?【匿名形式】
typescriptuoploadPicHead: { Authorization: 'Bearer ' + getToken() } as { Authorization : string },
✅File是什么类型?
typescriptTypescript 为 File 定义了一个类型,它代表 JavaScript 中的文件对象。 function processFile(file: File) { }
✅
this.$refs.mapchild as InstanceType<typeof MapAddr>
✅Props为什么要用
PropType
<> 重定义类型?https://v3.cn.vuejs.org/guide/typescript-support.html#注解-props
typescripttagIds: { type: Array as PropType<string[]>, default: () => [] }, coopModeType: { type: Number as PropType<1 | 2 | 3 | 4>, default: 1 }, selectPersonnelList: { type: Array as PropType<Array<ISelectPersonnelList>>, default: () => [] },
✅callback 函数如何定义?
typescript// 声明一个不带参数且不返回任何值的函数 type CallbackFunc = () => void; // 接受零个或多个任何类型的参数且不返回任何值的函数 type CallbackFuncParams = (...args: any[]) => void; // 指定一组其他参数(例如,字符串,数字,然后是一组额外的args) type CallbackFuncSomeParams = (arg1: string, arg2: number, ...args: any[]) => void;
✅property 'href' does not exist on type 'htmlelement
typescriptHTMLElemet 元素没有 href <a>, <link>, <area>, <base>. <style> HTMLAnchorElement, HTMLLinkElement, HTMLAreaElement, HTMLBaseElement. HTMLStyleElement
✅
Promise<any>
的 any表示 resolve(value) 的 value的类型✅用冒号还是断言?
typescriptconst tempForm:IForm = { ...this.investDialog.form }; const tempForm = { ...this.investDialog.form } as IForm; // 这种都要用ITabObj吗? computed: { tabList(): ITabObj[] { return this.$route.meta.tabList as ITabObj[]; } },
📌冒号比as好。
✅这种问题表示Vue组件未改造成TS
render ?: (any) => VNode
💡参数写any
render?: (arg0: (any)=>VNode, any) => VNode
HTTP header对象类型怎么定义?
header属性:https://cloud.tencent.com/developer/section/1189880
data中访问this,该如何定义?
debounce等高阶函数定义,需要访问this的, 先在data里定义成函数,然后在created里面赋值;
💡
disabled: (this as any).disabledFn
data中的函数如何定义?
typescripttype CallbackFuncParams = (...args: any[]) => void; data() { return { // debounce: () => {} debounce: (() => {}) as CallbackFuncParams }; },
as unknown as
typescriptthis.selectList as unknown as Array<{ label: string; isUser: boolean }>
💡改成as unknown as 就对了
如何在Typescript中定义Promise的返回值类型?
this.$refs.tree as InstanceType<Tree>
需要改成this.$refs.tree as Tree
✅ 事件Event的ts类型是Event
e.target.files[0]的修改:
const file: File = ((e.target as HTMLInputElement).files as FileList)[0];
✅Could not find a declaration file for module 'lodash'
typescript方案1: 在 shims-vue.d.ts 文件中加入代码 declare module 'lodashe' 方案2: npm i --save-dev @types/lodash
定义的一个对象obj的
type ?: string
, 然后在使用的时候:if(obj.type) {} // TSERROR type does not exist on obj
如何在Typescript中定义Promise的返回值类型
默认的情况下,main的类型为Promise<unknown>
const main = new Promise((resolve, reject) => {
resolve(1)
})
很明显,resolve了一个数字,如何使得main的类型为 Promise<number>
?
方法一:通过 Promise 的构造函数,声明返回值的泛型类型。
const main = new Promise<number>((resolve, reject) => {
resolve(1)
})
方法二:设置resolve参数的类型
const main = new Promise((resolve: (arg:number) => void, reject) => {
resolve(1)
})
参考链接:
https://www.jianshu.com/p/4f78b2a294cc
ts自动编译
npm i -g typescript
tsc --init
vscode — 终端 — 运行任务 — 监视tsconfig.json 即可。
需要修改部分
- [ ]
<script lang='ts'>
- [ ] Props为什么要用PropType<> 重定义类型
import Vue, { PropType } from 'vue';
- [ ] import加入
.vue
后缀(import的引入需要放到最上面) - [ ]
Vue.extend
包裹 - [ ] return data里面的对象,数组类型使用断言
- [ ] computed需要返回值
- [ ] methods 方法里面函数参数类型和返回值类型
- [ ] ref 内部组件对象
this.$refs.tree as Tree``this.$refs.mapchild as InstanceType<typeof MapAddr>
- [ ]
this.$route.query
因为可以通过 id=1&id=2 方式传数组,query.xxx 的值类型为: string | string[], 所以使用时需要通过 as string 强转为字符串。 this.keywords = this.$route.query.keywords as string; - [ ] ref 外部组件对象 需要编写d.ts声明文件
- [ ] this.$route.query.keywords as string; 因为可以通过 id=1&id=2 方式传数组,query.xxx 的值类型为: string | string[], 所以使用时需要通过 as string 强转为字符串。
其他修改:
- [ ] 注释掉不用的代码。
其他:
- 通过Vue.prototype | mixin 添加的属性与方法需在types/vue.d.ts定义