11 为函数和函数参数定义类型

本小节我们来学习函数类型的定义,以及对函数参数的详细介绍。前面我们在讲object例子的时候见过简单的函数定义,在那个例子中我们学习了如何简单地为一个参数指定类型。在本小节你将学习三种定义函数类型的方式,以及关于参数的三个知识——即可选参数、默认参数和剩余参数。接下来我们开始学习。 2.8.1. 函数类型 (1) 为函数定义类型 我们可以给函数定义类型,这个定义包括对参数和返回值的类型定义,我们先来看简单的定义写法: function add(arg1: number, arg2: number): number { return x + y; } // 或者 const add = (arg1: number, arg2: number): number => { return x + y; }; 在上面的例子中我们用function和箭头函数两种形式定义了add函数,以展示如何定义函数类型。这里参数 arg1 和 arg2 都是数值类型,最后通过相加得到的结果也是数值类型。 如果在这里省略参数的类型,TypeScript 会默认这个参数是 any 类型;如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;如果函数有返回值,那么 TypeScript 会根据我们定义的逻辑推断出返回类型。 (2) 完整的函数类型 一个函数的定义包括函数名、参数、逻辑和返回值。我们为一个函数定义类型时,完整的定义应该包括参数类型和返回值类型。上面的例子中,我们都是在定义函数的指定参数类型和返回值类型。接下来我们看下,如何定义一个完整的函数类型,以及用这个函数类型来规定一个函数定义时参数和返回值需要符合的类型。先来看例子然后再进行解释: let add: (x: number, y: number) => number; add = (arg1: number, arg2: number): number => arg1 + arg2; add = (arg1: string, arg2: string): string => arg1 + arg2; // error 上面这个例子中,我们首先定义了一个变量 add,给它指定了函数类型,也就是(x: number, y: number) => number,这个函数类型包含参数和返回值的类型。然后我们给 add 赋了一个实际的函数,这个函数参数类型和返回类型都和函数类型中定义的一致,所以可以赋值。后面我们又给它赋了一个新函数,而这个函数的参数类型和返回值类型都是 string 类型,这时就会报如下错误:...

March 13, 2024 · zlzong

12 使用泛型拯救你的any

在前面的小节中我们学习了any类型,当我们要表示一个值可以为任意类型的时候,则指定它的类型为any,比如下面这个例子: const getArray = (value: any, times: number = 5): any[] => { return new Array(times).fill(value); }; 这个函数接受两个参数。第一个参数为任意类型的值,第二个参数为数值类型的值,默认为 5。函数的功能是返回一个以 times 为元素个数,每个元素都是 value 的数组。这个函数我们从逻辑上可以知道,传入的 value 是什么类型,那么返回的数组的每个元素也应该是什么类型。 接下来我们实际用一下这个函数: getArray([1], 2).forEach(item => { console.log(item.length); }); getArray(2, 3).forEach(item => { console.log(item.length); }); 我们调用了两次这个方法,使用 forEach 方法遍历得到的数组,在传入 forEach 的函数中获取当前遍历到的数组元素的 length 属性。第一次调用这个方法是没问题的,因为我们第一次传入的值为数组,得到的会是一个二维数组[ [1], [1] ]。每次遍历的元素为[1],它也是数组,所以打印它的 length 属性是可以的。而我们第二次传入的是一个数字 2,生成的数组是[2, 2, 2],访问 2 的 length 属性是没有的,所以应该报错,但是这里却不会报错,因为我们在定义getArray函数的时候,指定了返回值是any类型的元素组成的数组,所以这里遍历其返回值中每一个元素的时候,类型都是any,所以不管做任何操作都是可以的,因此,上面例子中第二次调用getArray的返回值每个元素应该是数值类型,遍历这个数组时我们获取数值类型的length属性也没报错,因为这里item的类型是any。 所以要解决这种情况,泛型就可以搞定,接下来我们来学习泛型。 2.9.1. 简单使用 要解决上面这个场景的问题,就需要使用泛型了。泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。 还拿上面这个例子中的逻辑来举例,我们既要允许传入任意类型的值,又要正确指定返回值类型,就要使用泛型。我们先来看怎么改写: const getArray = <T>(value: T, times: number = 5): T[] => { return new Array(times)....

March 13, 2024 · zlzong

13 TS中的类,小心它与ES标准的差异

虽然说类是 ES6 中新增的概念,但是在这里讲 TS 中的类,是因为在语法的实现上 TS 和 ES6 规范的,还是有点区别。在学习本节课之前,你要确定你已经详细学习了ES6标准的类的全部知识,如果没有学习,建议你先学习下阮一峰的《ECMAScript 6 入门》,学习完后再来学习本节课你会发现,一些同样的功能写法上却不同。 2.10.1. 基础 类的所有知识我们已经在 ES6 中的类两个课时学过了,现在我们先来看下在 TS 中定义类的一个简单例子: class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } getPosition() { return `(${this.x}, ${this.y})`; } } const point = new Point(1, 2); 我们首先在定义类的代码块的顶部定义两个实例属性,并且指定类型为 number 类型。构造函数 constructor 需要传入两个参数,都是 number 类型,并且把这两个参数分别赋值给两个实例属性。最后定义了一个定义在类的原型对象上的方法 getPosition。 同样你也可以使用继承来复用一些特性: class Parent { name: string; constructor(name: string) { this.name = name; } } class Child extends Parent { constructor(name: string) { super(name); } } 这些和 ES6 标准中的类没什么区别,如果大家不了解ES6标准中类关于这块的内容,建议大家先去学习ES6类的知识。...

March 13, 2024 · zlzong

14 类型推论,看TS有多懂你

在学习基础部分的章节时,我们讲过,在一些定义中如果你没有明确指定类型,编译器会自动推断出适合的类型;比如下面的这个简单例子: let name = "lison"; name = 123; // error 不能将类型“123”分配给类型“string” 我们看到,在定义变量 name 的时候我们并没有指定 name 的类型,而是直接给它赋一个字符串。当我们再给 name 赋一个数值的时候,就会报错。在这里,TypeScript 根据我们赋给 name 的值的类型,推断出我们的 name 的类型,这里是 string 类型,当我们再给 string 类型的 name 赋其他类型值的时候就会报错。 这个是最基本的类型推论,根据右侧的值推断左侧变量的类型,接下来我们看两个更复杂的推论。 3.1.1 多类型联合 当我们定义一个数组或元组这种包含多个元素的值的时候,多个元素可以有不同的类型,这种时候 TypeScript 会将多个类型合并起来,组成一个联合类型,来看例子: let arr = [1, "a"]; arr = ["b", 2, false]; // error 不能将类型“false”分配给类型“string | number” 可以看到,此时的 arr 的元素被推断为string | number,也就是元素可以是 string 类型也可以是 number 类型,除此两种类型外的类型是不可以的。再来看个例子: let value = Math.random() * 10 > 5 ? 'abc' : 123 value = false // error 不能将类型“false”分配给类型“string | number” 这里我们给value赋值为一个三元操作符表达式,Math....

March 13, 2024 · zlzong

15 类型兼容性,开放心态满足灵活的JS

我们知道JavaScript是弱类型语言,它对类型是弱校验,正因为这个特点,所以才有了TypeScript这个强类型语言系统的出现,来弥补类型检查的短板。TypeScript在实现类型强校验的同时,还要满足JavaScript灵活的特点,所以就有了类型兼容性这个概念。本小节我们就来全面学习一下TypeScript的类型兼容性。 3.2.1 函数兼容性 函数的兼容性简单总结就是如下六点: (1) 函数参数个数 函数参数个数如果要兼容,需要满足一个要求:如果对函数 y 进行赋值,那么要求 x 中的每个参数都应在 y 中有对应,也就是 x 的参数个数小于等于 y 的参数个数,来看例子: let x = (a: number) => 0; let y = (b: number, c: string) => 0; 上面定义的两个函数,如果进行赋值的话,来看下两种情况的结果: y = x; // 没问题 将 x 赋值给 y 是可以的,因为如果对函数 y 进行赋值,那么要求 x 中的每个参数都应在 y 中有对应,也就是 x 的参数个数小于等于 y 的参数个数,而至于参数名是否相同是无所谓的。 x = y; // error Type '(b: number, s: string) => number' is not assignable to type '(a: number) => number' 这个例子中,y 要赋值给 x,但是 y 的参数个数要大于 x,所以报错。...

March 13, 2024 · zlzong

16 使用类型保护让TS更聪明

这个小节我们来学习类型保护,在学习前面知识的时候我们有遇到过需要告诉编译器某个值是指定类型的场景,当时我们使用的是类型断言,这一节我们来看一个不同的场景: const valueList = [123, "abc"]; const getRandomValue = () => { const number = Math.random() * 10; // 这里取一个[0, 10)范围内的随机值 if (number < 5) return valueList[0]; // 如果随机数小于5则返回valueList里的第一个值,也就是123 else return valueList[1]; // 否则返回"abc" }; const item = getRandomValue(); if (item.length) { // error 类型“number”上不存在属性“length” console.log(item.length); // error 类型“number”上不存在属性“length” } else { console.log(item.toFixed()); // error 类型“string”上不存在属性“toFixed” } 上面这个例子中,getRandomValue 函数返回的元素是不固定的,有时返回数值类型,有时返回字符串类型。我们使用这个函数生成一个值 item,然后接下来的逻辑是通过是否有 length 属性来判断是字符串类型,如果没有 length 属性则为数值类型。在 js 中,这段逻辑是没问题的,但是在 TS 中,因为 TS 在编译阶段是无法知道 item 的类型的,所以当我们在 if 判断逻辑中访问 item 的 length 属性的时候就会报错,因为如果 item 为 number 类型的话是没有 length 属性的。...

March 13, 2024 · zlzong

17 使用显式复制断言给TS一个你一定会赋值的承诺

在讲解本小节的主要内容之前,我们先来补充两个关于null和undefined的知识点: (1) 严格模式下null和undefined赋值给其它类型值 当我们在 tsconfig.json 中将 strictNullChecks 设为 true 后,就不能再将 undefined 和 null 赋值给除它们自身和void 之外的任意类型值了,但有时我们确实需要给一个其它类型的值设置初始值为空,然后再进行赋值,这时我们可以自己使用联合类型来实现 null 或 undefined 赋值给其它类型: let str = "lison"; str = null; // error 不能将类型“null”分配给类型“string” let strNull: string | null = "lison"; // 这里你可以简单理解为,string | null即表示既可以是string类型也可以是null类型 strNull = null; // right strNull = undefined; // error 不能将类型“undefined”分配给类型“string | null” 注意,TS 会将 undefined 和 null 区别对待,这和 JS 的本意也是一致的,所以在 TS 中,string|undefined、string|null和string|undefined|null是三种不同的类型。 (2) 可选参数和可选属性 如果开启了 strictNullChecks,可选参数会被自动加上|undefined,来看例子: const sum = (x: number, y?...

March 13, 2024 · zlzong

18 类型别名和字面量类型—单调的类型

本小节我们来学习类型别名和字面量类型。类型别名我们之前在讲泛型的时候接触过,现在来详细学习下。 3.5.1 类型别名 类型别名就是给一种类型起个别的名字,之后只要使用这个类型的地方,都可以用这个名字作为类型代替,但是它只是起了一个名字,并不是创建了一个新类型。这种感觉就像 JS 中对象的赋值,你可以把一个对象赋给一个变量,使用这个对象的地方都可以用这个变量代替,但你并不是创建了一个新对象,而是通过引用来使用这个对象。 我们来看下怎么定义类型别名,使用 type 关键字: type TypeString = string; let str: TypeString; str = 123; // error Type '123' is not assignable to type 'string' 类型别名也可以使用泛型,来看例子: type PositionType<T> = { x: T; y: T }; const position1: PositionType<number> = { x: 1, y: -1 }; const position2: PositionType<string> = { x: "right", y: "top" }; 使用类型别名时也可以在属性中引用自己: type Child<T> = { current: T; child?: Child<T>; }; let ccc: Child<string> = { current: "first", child: { // error current: "second", child: { current: "third", child: "test" // 这个地方不符合type,造成最外层child处报错 } } }; 但是要注意,只可以在对象属性中引用类型别名自己,不能直接使用,比如下面这样是不对的:...

March 13, 2024 · zlzong

19 使用可辨识联合并保证每个case都被处理

我们可以把单例类型、联合类型、类型保护和类型别名这几种类型进行合并,来创建一个叫做可辨识联合的高级类型,它也可称作标签联合或代数数据类型。 所谓单例类型,你可以理解为符合单例模式的数据类型,比如枚举成员类型,字面量类型。 可辨识联合要求具有两个要素: 具有普通的单例类型属性(这个要作为辨识的特征,也是重要因素)。 一个类型别名,包含了那些类型的联合(即把几个类型封装为联合类型,并起一个别名)。 来看例子: interface Square { kind: "square"; // 这个就是具有辨识性的属性 size: number; } interface Rectangle { kind: "rectangle"; // 这个就是具有辨识性的属性 height: number; width: number; } interface Circle { kind: "circle"; // 这个就是具有辨识性的属性 radius: number; } type Shape = Square | Rectangle | Circle; // 这里使用三个接口组成一个联合类型,并赋给一个别名Shape,组成了一个可辨识联合。 function getArea(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math....

March 13, 2024 · zlzong

20 this,类型?

在 JavaScript 中,this 可以用来获取对全局对象、类实例对象、构建函数实例等的引用,在 TypeScript 中,this 也是一种类型,我们先来看个计算器 Counter 的例子: class Counter { constructor(public count: number = 0) {} add(value: number) { // 定义一个相加操作的方法 this.count += value; return this; } subtract(value: number) { // 定义一个相减操作的方法 this.count -= value; return this; } } let counter = new Counter(10); console.log(counter.count); // 10 counter.add(5).subtract(2); console.log(counter.count); // 13 我们给 Counter 类定义几个方法,每个方法都返回 this,这个 this 即指向实例,这样我们就可以通过链式调用的形式来使用这些方法。这个是没有问题的,但是如果我们要通过类继承的形式丰富这个 Counter 类,添加一些方法,依然返回 this,然后采用链式调用的形式调用,在过去版本的 TypeScript 中是有问题的,先来看我们继承的逻辑: class PowCounter extends Counter { constructor(public count: number = 0) { super(count); } pow(value: number) { // 定义一个幂运算操作的方法 this....

March 13, 2024 · zlzong