TypeScript具有类型系统,且是JavaScript的超集。它可以编译成普通的JavaScript代码。TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。
- $ cnpm install -g typescript
- $ tsc -v
- # Version 5.0.4
src/01app.ts 文件:
- // 定义了一个 string类型的 变量
- let title: string = '城东书院';
- 在命令行里输入以下命令都可以将.ts文件编译为.js文件:
-
- # 使用tsc指令将src文件夹下的 01app.ts 文件转换输出到 dist目录下的app.js
- $ tsc ./src/01app.ts --outFile ./dist/01app.js
- $ tsc ./src/01app.ts --outDir ./dist
在编辑器,将下面的代码输入到 src/02greeter.ts 文件里。我们注意到 person: string,表示 string 是 person 函数的参数类型注解:
- // function greeter (a, b, c) {}
- // function greeter (a: string, b: number, c: boolean) {}
- // function greeter (a: string, b: number, c: boolean): string {} 函数的返回值为string类型
- // function test (): void {} void 代表该函数没有返回值
- function greeter (person: string): string {
- return 'hello' + person
- }
-
- console.log(greeter('城东书院'))
TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。在这个例子里,我们希望 greeter函数接收一个字符串参数。然后尝试把 greeter的调用改成传入一个数组:
- // function greeter (a, b, c) {}
- // function greeter (a: string, b: number, c: boolean) {}
- // function greeter (a: string, b: number, c: boolean): string {} 函数的返回值为string类型
- // function test (): void {} void 代表该函数没有返回值
- function greeter (person: string): string {
- return 'hello' + person
- }
-
- console.log(greeter('城东书院'))
- // Argument of type 'number' is not assignable to parameter of type 'string'.
- // 类型“number”的参数不能赋给类型“string”的参数
- // console.log(greeter(100))
重新编译,你会看到产生了一个错误:
- Argument of type 'number' is not assignable to parameter of type 'string'.
- // function greeter (a, b, c) {}
- // function greeter (a: string, b: number, c: boolean) {}
- // function greeter (a: string, b: number, c: boolean): string {} 函数的返回值为string类型
- // function test (): void {} void 代表该函数没有返回值
-
- // 声明式函数
- // function greeter (person: string): string {
- // return 'hello' + person
- // }
-
- // 定义了变量 该变量是一个函数
- // const greeter: (person: string) => string 定义了一个函数,且该函数有string类型的参数,并且函数的返回值为 string
- // 此处的 => 不是箭头函数 表示ts的函数的返回值类型
- // const greeter: (person: string) => string = function (person: string): string {
- // return 'hello' + person
- // }
-
- // const greeter = (person) => {
- // return 'hello' + person
- // }
-
- // 第一个 => 表示函数的返回值为 string
- // 第二个 => 表示此处是一个函数
- const greeter: (person: string) => string = (person: string):string => {
- return 'hello' + person
- }
-
- console.log(greeter('城东书院'))
- // Argument of type 'number' is not assignable to parameter of type 'string'.
- // 类型“number”的参数不能赋给类型“string”的参数
- // console.log(greeter(100))
类似地,尝试删除greeter调用的所有参数。TypeScript会告诉你使用了非期望个数的参数调用了这个函数。在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
要注意的是尽管有错误,greeter.js文件还是被创建了。就算你的代码里有错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。
让我们开发这个示例应用。这里我们使用接口来描述一个拥有firstName和lastName字段的对象。在TypeScript里,只在两个类型内部的结构兼容,那么这两个类型就是兼容的。这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements语句。
- // 如果要给某一个位置添加 对象的类型, 使用接口去解决文通
-
- interface IUser { // 实际上就相当于你自己定义了一个数据类型
- firstName: string; // 结尾 可以 什么也不写,也可以写为 , 还可以写为;
- lastName: string;
- }
-
- // // user 是一个对象 需要通过 接口 来定义对象的数据类型
- // function greeter1 (user: IUser): string {
- // return 'hello' + user.firstName + ' ' + user.lastName
- // }
-
-
- // function greeter1 (user: { firstName: string, lastName: string }): string {
- // return 'hello' + user.firstName + ' ' + user.lastName
- // }
-
-
- const greeter1: (user: IUser) => string = (user: IUser): string => {
- return 'hello' + user.firstName + ' ' + user.lastName
- }
- console.log(greeter1({ firstName: '青山', lastName: '哥哥'}))
最后,让我们使用类来改写这个例子。TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。让我们创建一个Student类,它带有一个构造函数和一些公共字段。注意类和接口可以一起工作。
还要注意的是,在构造函数的参数上使用public等同于创建了同名的成员变量。
注:public修饰符会引发 Parameter 'firstName' implicitly has an 'any' type.,解决方法是在tsconfig.json文件中,添加"noImplicitAny": false,或者将"strict": true,改为"strict": false
- // src/04test.ts
- class Student {
- fullName: string;
- // public 等同于创建了同名的成员变量
- // constructor (public firstName: string, public lastName: string) {
- // this.fullName = this.firstName + this.lastName
- // }
-
- firstName: string; // 成员变量
- lastName: string// 成员变量
- constructor (firstName: string, lastName: string) {
- this.firstName = firstName
- this.lastName = lastName
- this.fullName = this.firstName + this.lastName
- }
- }
-
- interface IPerson {
- firstName: string
- lastName: string
- }
-
- function greeter2 (user: IPerson): string {
- return 'hello' + user.firstName + user.lastName
- }
-
- const stu = new Student('青山', '哥哥')
-
- console.log(greeter2(stu))
最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean。
- let isDone: boolean = false
和JavaScript一样,TypeScript里的所有数字都是浮点数。这些浮点数的类型是number。
- let decLiteral: number = 6
TypeScript像其它语言里一样,使用string表示文本数据类型。和JavaScript一样,可以使用双引号(")或单引号(')表示字符串。
- let from: string = "城东书院"
- from = "家"
也使用模版字符串,定义多行文本和内嵌表达式。这种字符串是被反引号包围(`),并且以${ expr }这种形式嵌入表达式。
- let surname: string = `Felix`
- let age: number = 37
- let sentence: string = `Hello, my name is ${ surname }.
-
- I'll be ${ age + 1 } years old next month.`
TypeScript像JavaScript一样可以操作数组元素。有两种方式可以定义数组。第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:
- let list: number[] = [1, 2, 3]
第二种方式是使用数组泛型,Array<元素类型>:
- let list: Array<number> = [1, 2, 3]
- // src/05arr.ts
-
- // ts中使用数组有两种方式
- // 数据类型[]
- // 泛型 Array<数据类型>
- // 数据类型
- // string number boolean .... interface
- const arr1: number[] = [1, 2, 3]
- const arr2: Array<string> = ['a', 'b', 'c']
-
- interface IUser1 {
- username: string
- password: string
- }
-
- const arr3: IUser1[] = [
- { username: '城东书院', password: '123456' }
- ]
- const arr4: Array<IUser1> = [
- { username: '城东书院', password: '123456' }
- ]
-
- const arr5: {username: string, password: string }[] = [
- { username: '城东书院', password: '123456' }
- ]
-
- const arr6: Array<{username: string, password: string | number }> = [
- { username: '城东书院', password: '123456' }
- ]
-
- const arr7: Array<number | string> = [1, 2, 3, 'a'] // 推荐
- const arr8: (number | string)[]= [1, 2, 3, 'a'] // 不推荐
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为 string和number类型的元组。
- // 声明一个元组类型 x
- let x: [string, number]
- // 初始化 x
- x = ['hello', 10] // OK
- // 无效的初始值
- x = [10, 'hello'] // Error
- 当访问一个已知索引的元素,会得到正确的类型:
-
- console.log(x[0].substr(1)) // OK
- console.log(x[1].substr(1)) // Error, 'number' 不存在 'substr' 方法
- 当访问一个越界的元素,会出现错误:
-
- x[3] = "world" // Error, '[string, number]' 未定义第 3 个元素的类型.
- console.log(x[5].toString()) // Error, '[string, number]' 未定义第 5 个元素的类型.
- // src/06tuple.ts
- // 元组类型允许表示一个**已知元素数量和类型的数组**,各元素的类型不必相同
-
- // 只能说明你的元素可以是string类型或者是number类型,但是不知道每个元素的具体的数据类型
- const arr11: Array<string | number> = [1, 2, 'a', 3, 'b']
-
- // 元祖
- const arr22: [number, number, string, number, string] = [1, 2, 'a', 3, 'b'] // ✅
-
- const arr33: [number, string, string, number, string] = [1, 2, 'a', 3, 'b'] // ❌
-
- arr33[1] = 'c' // ✅
- arr33[1] = 66 // ❌
enum类型是对JavaScript标准数据类型的一个补充。使用枚举类型可以为一组数值赋予友好的名字。
- enum Color {Red, Green, Blue}
- let c: Color = Color.Green
- 默认情况下,从 0 开始为元素编号。你也可以手动的指定成员的数值。例如,我们将上面的例子改成从 1 开始编号:
-
- enum Color {Red = 1, Green, Blue}
- let c: Color = Color.Green
- 或者,全部都采用手动赋值:
-
- enum Color {Red = 1, Green = 2, Blue = 4}
- let c: Color = Color.Green
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
- enum Color {Red = 1, Green, Blue}
- let colorName: string = Color[2]
- console.log(colorName) // 'Green'
- //enum.ts
-
- // src/07enum.ts
- // enum类型是对JavaScript标准数据类型的一个补充。使用枚举类型可以为一组数值赋予友好的名字
- // 周一-周日 ✅
- // 1月-12月 ✅
- // 上右下左 ✅
- // 颜色三原色 ✅
- // 1-31天 ❌
-
- // enum Color { Red, Green, Blue }
- // console.log(Color.Red) // 0
- // console.log(Color.Green) // 1
- // console.log(Color.Blue) // 2
-
- enum Color { Red = 10, Green, Blue }
- console.log(Color.Red) // 10
- console.log(Color.Green) // 11
- console.log(Color.Blue) // 12
-
-
- enum Direction {
- UP = 38,
- RIGHT = 39,
- DOWN = 40,
- LEFT = 37 // event.keyCode === 37 event.keyCode === Direction.LEFT
- }
-
- // 不仅可以复制索引值,也可以赋值字符串
- enum Week {
- Monday = '星期一',
- Tuesday = '星期二',
- Wednesday = '星期三',
- Thursday = '星期四',
- Friday = '星期五',
- Saturday = '星期六',
- Sunday = '星期日'
- }
-
- const a: Week = Week.Saturday
- console.log(a)
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。那么我们可以使用any类型来标记这些变量:
- let notSure: any = 4
- notSure = "maybe a string instead" // OK 赋值了一个字符串
- notSure = false // OK 赋值了一个布尔值
在对现有代码进行改写的时候,any 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。
- let notSure: any = 4
- notSure.ifItExists() // okay, ifItExists函数在运行时可能存在
- notSure.toFixed() // okay, toFixed 函数存在 (在编译时不做检查)
当你只知道一部分数据的类型时,any 类型也是有用的。比如,你有一个数组,它包含了不同的类型的数据:
- let list: any[] = [1, true, "free"]
- list[1] = 100
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:
- function echo(): void {
- console.log('唯见青山,不见君')
- }
- 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
-
- let unusable: void = undefined
- let greeting: void = 'hello world' // void 类型不能赋值为字符串
- // src/08assert.ts
- // a as b a 断言为 b数据类型
-
- // const val: string = 'hello'
- // let val = '10000' // 鼠标移上去 可以看到 val的数据类型为string
-
- // let len = val.length
-
- let val
- // let len = val.length // len any
- let len = (val as string).length // len number
-
-
- // const div = document.createElement('div')
- // div.style.color = '#f66'
-
-
- const div = document.getElementById('div');
- // div.style.color = '#f66' // div”可能为 “null”
- (div as HTMLDivElement).style.color = "#f66"
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
- interface IPerson {
- name: string
- age: number
- }
-
- let tom: IPerson = {
- name: 'Tom',
- age: 25
- }
- 上面的例子中,我们定义了一个接口 IPerson,接着定义了一个变量 tom,它的类型是 IPerson。这样,我们就约束了 tom 的形状必须和接口 IPerson 一致。接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。定义的变量比接口少了一些属性是不允许的:
-
- interface IPerson {
- name: string
- age: number
- }
-
- let tom: IPerson = {
- name: 'Tom'
- }
- // Property 'age' is missing in type '{ name: string }' but required in type 'Person'.
- 多一些属性也是不允许的:
-
- interface Person {
- name: string
- age: number
- }
-
- let tom: Person = {
- name: 'Tom',
- age: 25,
- gender: 'male'
- }
-
- // Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
- // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见, 赋值的时候,变量的形状必须和接口的形状保持一致。
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
- interface Person {
- name: string
- age?: number
- }
-
- let tom: Person = {
- name: 'Tom'
- }
- interface Person {
- name: string
- age?: number
- }
-
- let tom: Person = {
- name: 'Tom',
- age: 25
- }
可选属性的含义是该属性可以不存在。
这时仍然不允许添加未定义的属性:
- interface Person {
- name: string
- age?: number
- }
-
- let tom: Person = {
- name: 'Tom',
- age: 25,
- gender: 'male'
- }
-
- // Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
- // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
- interface Person {
- name: string
- age?: number
- [propName: string]: any
- }
-
- let tom: Person = {
- name: 'Tom',
- gender: 'male'
- }
使用 [propName: string] 定义了任意属性取 string 类型的值。需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:写为any即可
- interface Person {
- name: string
- age?: number
- [propName: string]: string
- }
-
- let tom: Person = {
- name: 'Tom',
- age: 25,
- gender: 'male'
- }
-
- // Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.
- // Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
- // Property 'age' is incompatible with index signature.
- // Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number name: string age: number gender: string },这是联合类型和接口的结合。
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
- interface Person {
- readonly id: number
- name: string
- age?: number
- [propName: string]: any
- }
-
- let tom: Person = {
- id: 89757,
- name: 'Tom',
- gender: 'male'
- }
-
- tom.id = 9527
- // Cannot assign to 'id' because it is a read-only property.