经常听到小伙伴说 Angular 很麻烦,又要学 RxJS,又要学 TS。讲道理,RxJS 理解起来确实需要时间,但是 TS 使用起来应该不会很难,特别是从工具使用者的角度来说,应该不太难理解才对。
TS 中的几个概念
- TS 是 JS 的超集。或者说,JS 也是 TS。如何理解呢?比如说,你把任意一个 JS 文件后缀换成 TS,用 TS 的方式运行,跟直接运行 JS 是一样的。
// 以下代码在 TS 中运行也是成立的。 const a = 1; function add(a, b) { return a + b; } - TS = JS + Type, 也就是说,TS 是加上了类型约束的 JS。
const a :number = 1; function add(a: number, b: number) { return a + b; } - TS 的类型并不是运行时,而是主要为编辑器服务的,编译时。试图在运行时或者变量的类型是无法被保证的(除了 JS 中支持的判断类型的方法)
TS 中类型的使用
- JS 中的基本类型:
let a: number let b: string - Function 的参数和返回值
function func(a: string): string {} (a: string): string => { } - 当然,因为 JS 中 Function 也是 Object,本省也就是一种类型的 Object
let func: (a: string) => string // 注意,这里定义 Func 的类型和 arrow function 是不同的 - 除了基本类型以外,跟其他语言一样,class 当然也是一种类型:
class Test { } const test: Test = new Test(); - 我们知道 JS 中是可以直接使用
{}来创建对象的,而非一定需要借助于class。对于object, 我们可以直接给object定义类型。const test: { a: number; b: number; } = { a: 1, b: 2 }; // 当然也可以给它取个名字,通过 `interface` 关键字 interface Test { a: string; b: string; } // 或者 `type` 关键字 type Test { a: number; b: number; } - 数组也是类似的:
const data: string[] - 类型不仅仅可以是基础类型,class, function,interface, 它甚至可以是具体的值
const a: 1 = 1; // 当然,也可以是多个值 const a: 1 | 2 | 3 = 1; // 很自然的 | 两边可以是各种类型 const a: 1 | string | (arg: any[]) => void
TS 中的类型推断
TS 并不像某些强类型语言一样,必须要清楚的指定变量的类型,TS 在一些条件下可以动态的推荐类型。
-
变量根据赋值内容推断类型:
let a = 1 a = '' // Type 'string' is not assignable to type 'number' -
Function 根据代码推断返回类型
function add(a: number, b: number) { return a + b; } let c: string = add(1, 2); // Type 'number' is not assignable to type 'string'
除了以上对于省略类型的推断,TS 代码逻辑中进行类型推断,以便缩小变量的 scope, 主要针对 if block 中的类型进行推断。
逻辑也很简单,如果 if 条件中,如果能够明确类型,在当前 block 中,会以推断的类型使用。因为 if 是具体的业务逻辑,所以,它一定也是满足 JS 语法的代码。所以 JS 中,能够判断类型的方法不外乎以下几种:
- typeof 用来判断 JS 中的类型 number / string / function / object
function addOne(a: number | string) { if (typeof a === 'string') { return a + '_1'; // (parameter) a: string } return a + 1; // (parameter) a: number } - empty check。当然我们也可以通过 if 来处理 block 中是否为空。
function addOne(a?: number | string) { if (a) { console.log(a); // (parameter) a: string | number } } - instanceof 判断 class
与 typeof 类似,不做赘述。 - in 判断 interface。实际上,在运行时,代码并不知道任何 interface 相关的信息,那么我们如何区分不同的 interface 呢。答案是差异字段。
interface A { a: string; } interface B { a: string; b: string; } function testFunc(param: A | B) { if ('b' in param) { console.log(param); // (parameter) param: B } } - 当然,字段差异其实不仅仅是「有」,「没有」的差异,也可以是类型差异,前提是用来差异的字段必须是 union type:
interface A { type: 'a'; a: number; } interface B { type: 'b'; a: number; } function testFunc(param: A | B) { if (param.type === 'a') { console.log(param); // (parameter) param: B } }
类型操作(基于一个类型,创造另一个类型)
类型编程。如果说,TS 是给 JS 加上了类型的话,其实是不够全面的,TS 的强大在于,类型本身,也是可编程的。也就是说,我们可以基于一定的输入类型,创造出一个新的类型。当然,可以理解成,对于范型的编程。举个最简单的例子,我们需要写一个方法,修改 object 的 property 的值:
// 常规编程语言能做到的类型约束
function updateProperty<T>(obj: T, key: string, value: any):void {
}
这里的问题,很明显,既然已经能够明确当前 obj 的类型,在 key,value 上是否可以给予明确的类型约束呢?
function updateProperty<
T extends object,
K extends keyof T,
V extends T[K]>(obj: T, key: K, value: V) {}
const obj: B = { type: 'b', a: '111' };
updateProperty(obj, 'a', 1111); // Argument of type 'number' is not assignable to parameter of type 'string'.
TS 中为类型编程,当然核心是 create types。
- 范型 (类型编程的基础)
- keyof
- typeof
- indexed access types
- conditional types
- mapped types
- template literal types
很容易看出来,所谓类型编程,核心是对于 object 的类型编程
- 获得 object 所有 key 的类型:
keyofinterface A { type: 'a'; a: number; } type KeyOfA = keyof A; const key: keyof A = 'a'; - 获得 object key 对应 value 的类型:
T['a'] (indexed access types)interface A { type: 'a'; a: number; } let a: A['a'] = '11'; // Type 'string' is not assignable to type 'number' - 根据 key,value 的类型和对应关系,创造 object 类型 :
mapped types{ [key: string]: string } // 当然,key 也可以是一组 string / number / symbol (union type) { [key in 'a' | 'type']: string } // 很容易想到,我们可以根据其他类型生成 object 类型 type Test = { [key in keyof A]: A[key] }
非 object 相关
- 动态获得变量类型:
typeofconst currentValue: string = ''; type CurrentType = typeof currentValue; // type CurrentType = string - 类型的条件判断:
conditional types - 模版类型:
template literal types