一篇长文总结 TypeScript
须要掌握的基础,但愿对正在学或者想学 TypeScript
的看完这篇文章后的你,会对 TypeScript
有一个初步的理解。html
本文偏长,点赞👍获取一块记忆面包typescript
新建一个文件夹,如 TypeScript-Study
,输入以下命令行,在全局环境下安装 tsc
命令npm
npm install -g typescript
复制代码
安装完成以后,咱们就能够在任何地方执行 tsc
命令了。编程
ts
文件新建一个简单的文件 hello.ts
,复制以下代码到文件中,这里建议使用 VS Code
编译器,内置了 TypeScript
支持,并且自己也是用 TypeScript
编写的(毕竟微软的亲儿子)json
let hello: string = 'hello typescript'
console.log(hello)
复制代码
而后输入下面命令行执行后端
tsc hello.ts
复制代码
这时会发现文件夹多出了一个编译好的 js
文件 hello.js
数组
var hello = 'hello typescript';
console.log(hello);
复制代码
这里能够发现,在 ts
文件中使用 :
指定变量类型。bash
上面例子中咱们给 hello
参数指定了 string
类型,编译成 JavaScript
时,检查的代码并不会被插入到 js
文件中。编程语言
这是由于 TypeScript
只会进行静态检查,若是发现有错误,编译的时候就会报错。编辑器
下面修改一下代码,把定义了 string
类型的参数改成 number
类型的参数
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
复制代码
编辑器中会提示错误,编译的时候也会出错
但仍然不妨碍咱们执行生成 js
文件
var hello = 'hello typescript';
hello = 2;
console.log(hello);
复制代码
若是要在报错的时候终止 js
文件的生成,能够在 tsconfig.json
中配置 noEmitOnError
便可,这里就不展开说明。
JavaScript
的类型分为两种:原始数据类型和对象类型。
原始数据类型包括:布尔值、数值、字符串、null
、undefined
以及 Symbol
。
在 TypeScript
中,使用 boolean
定义布尔值类型
let isBoolean: boolean = true;
复制代码
注意:要注意 boolean 和 Boolean 的大小写区别, boolean 是布尔值类型,而 Boolean 是构造函数。这里除了 null 和 undefined 以外,别的基本类型都同样。
使用 number
定义数值类型
// 基本用法
let decLiteral: number = 1234567;
let hexLiteral: number = 0xf0ac;
let notANumber: number = NaN;
let infinity: number = Infinity;
复制代码
编译结果:
// 基本用法
var decLiteral = 1234567;
var hexLiteral = 0xf0ac;
var notANumber = NaN;
var infinity = Infinity;
复制代码
使用 string
定义字符串类型
let name: string = 'sam';
复制代码
使用 null
和 undefined
来定义这两个原始数据类型:
let u: undefined = undefined;
let n: null = null;
复制代码
在 TypeScript
中,咱们使用 interfaces
来定义对象类型。
我们直接举例说明吧
interface IPerson {
name: string;
age: number;
}
let sam: IPerson = {
name: 'sam',
age: 20
};
复制代码
上面的例子中,咱们定义了一个接口 IPerson
,而后定义了一个变量 sam
,变量的类型是 IPerson
,这样就约束了 sam
的形状必须和接口 IPerson
一致。
注意:为了良好的编写习惯,建议接口的名称加上 I 前缀。
定义的变量 sam
比接口多或者少属性都是会报错的
interface IPerson {
name: string;
age: number;
}
// 错误(比接口少了个age属性)
let sam1: IPerson = {
name: 'sam'
};
// 错误(比接口多了个gender属性)
let sam2: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
// 正确
let sam3: IPerson = {
name: 'sam',
age: 20
};
复制代码
那么怎么解决这个问题呢,这里有个可选属性
在接口上某个属性加个 ?
,代表不须要强制匹配该属性
interface IPerson {
name: string;
age?: number;
}
// 正确
let sam1: IPerson = {
name: 'sam'
};
// 错误(此时咱们仍是不能在接口上添加未定义的属性)
let sam2: IPerson = {
name: 'sam',
gender: 'man'
};
复制代码
为了解决添加未定义的属性,咱们可使用任意属性
在须要添加任意属性的接口使用 [propName: string]
, 一旦定义了任意属性,那么肯定属性和可选属性的类型都必须是它的类型的子集
interface IPerson {
name: string;
// 此时的age类型为number,不是string类型的子集
age?: number;
// 肯定属性和可选属性的类型都必须是string类型的子集
[propName: string]: string;
}
// 错误(此时的age类型为number,不是string类型的子集)
let sam1: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
复制代码
那么怎么解决这个问题呢,一个接口中只能定义一个任意属性。若是接口中有多个类型的属性,则能够在任意属性中使用联合类型
interface IPerson {
name: string;
age?: number;
// 此时肯定属性和可选属性的类型都必须是string或者number类型的子集
[propName: string]: string | number;
}
// 正确
let sam1: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
// 正确
let sam2: IPerson = {
name: 'sam',
gender: 'man'
};
// 正确
let sam3: IPerson = {
name: 'sam'
};
// 错误,任意属性未添加boolean类型
let sam4: IPerson = {
name: 'sam',
rich: false
};
复制代码
对象中的一些字段只能在建立的时候被赋值,后续没法更改
interface IPerson {
readonly id: number;
name: string;
age?: number;
[propName: string]: string | number;
}
let sam: IPerson = {
id: 123,
name: 'Tom',
gender: 'male'
};
// 错误,此时不能再次修改id的值
sam.id = 9527;
// Cannot assign to 'id' because it is a read-only property.
复制代码
到这,我想你大概就了解了对象的类型(接口)了
还记得上面的例子中,咱们在给 hello
赋值的时候编译器会报错
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
// Type '2' is not assignable to type 'string'.ts(2322)
复制代码
若是把 hello
改成 any
类型,则代表 hello
能够赋值为任意类型。
let hello: any = 'hello typescript'
hello = 2
console.log(hello) // 2
复制代码
固然这里不是但愿你全部的类型都用 any
,这还写个🔨的 TypeScript
,这里通常建议引入第三方库或者实在没法确认在处理什么类型时的时候才使用。
若是没有明确的指定类型,那么 TypeScript
会依照类型推论(Type Inference
)的规则推断出一个类型。
下面代码 hello
没有指定类型,但在编译器和编译时也会报错误提醒
let hello = 'hello typescript'
hello = 2
// Type '2' is not assignable to type 'string'.ts(2322)
复制代码
由于它等价于
let hello: string = 'hello typescript'
hello = 2
复制代码
可是若是定义的时候没有赋值,无论以后有没有赋值,都会被推断成 any
类型
let hello
hello = 'string2'
hello = 2
复制代码
等价于
let hello: any
hello = 'string2'
hello = 2
复制代码
有时候咱们但愿声明一个变量时候包含多个类型,那么咱们可使用 |
分隔每一个类型。还记得上面的例子吗
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
复制代码
此时咱们修改一下 hello
参数
let hello: string | number = 'hello typescript'
hello = 2
console.log(hello)
复制代码
这样就代表 hello
参数接受 string
和 number
类型
在 TypeScript
中,数组类型有多个定义的方式。因为本文是基础篇,笔者只讲比较经常使用的几种方法方便你们理解,后续进阶的用法笔者会另开文章说明。
let arr1: number[] = [1, 2, 3, 4, 5];
let arr2: string[] = ["one", "two", "third", "four", "five"];
let arr3: any[] = [1, "two", false, "four", 5];
复制代码
但这里要注意,定义后的数组 arr
的项中不容许出现非其余的类型( any
除外)
// 错误(two为string类型)
let arr1: number[] = [1, 'two', 3, 4, 5];
let arr2: number[] = [1, 2, 3, 4, 5];
// 错误(定义后的arr,push一个string类型是错误的)
arr2.push("six")
复制代码
用数组泛型(Array Generic) Array<elemType>
来表示数组
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,这里不作过多描述。
let arr1: Array<number> = [1, 2, 3, 4, 5];
let arr2: Array<string> = ["one", "two", "third", "four", "five"];
let arr3: Array<string | number | boolean> = [1, "two", false, "four", 5];
let arr4: Array<any> = [1, "two", false, "four", 5];
复制代码
咱们先来复习一下 JavaScript
里面声明常见的定义函数的方式(函数声明和函数表达式)
// 函数声明
function sum1(x + y) {
return x + y;
}
// 函数表达式
let sum2 = function (x, y) {
return
}
复制代码
在 TypeScript
声明函数时,咱们须要把函数的输入和输出都要考虑在内
function sum1 (x: number + y: number): number {
return x + y;
}
// 用法
// 错误
sum(1);
sum(1, 2, 3);
// 正确
sum(1, 2);
复制代码
可见,定义好的函数,若是输入了多的或者少的参数都是不被 TypeScript
容许的。
你可能会这样写一个函数表达式
let sum2 = function (x: number, y: number): number {
return x + y;
};
复制代码
这样只对右边的匿名函数进行了类型定义,而左边的参数 sum2
是经过类型推断出来的,并不算一个完整的 TypeScript
表达式。正确写法以下
let sum2: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y
}
复制代码
注意:在 TypeScript 的类型定义中的 => 用来表示函数的定义,箭头左边是输入类型,箭头右边是输出类型,不要和 ES6 的箭头函数混淆。
和接口的可选属性类似,咱们也能够用 ?
来给函数表示可选的参数
function sum3 (x:number, y?: number) {
return x + y
}
// 用法
sum3(1,2)
sum3(1)
复制代码
注意:可选参数的后面不容许放必须参数,由于这样调用的时候没法识别。
除非添加参数默认值,则可无视这个限制
// 错误写法(可选参数y后面跟着必须参数z)
function sum3 (x:number, y?:number, z: number) {
return x + y + z
}
// 给可选参数y添加参数默认值
function sum4 (x:number, y:number = 1, z: number) {
return x + y + z
}
复制代码
用来手动指定一个值的类型叫作类型断言。
有两种写法,尖括号 <类型>
和 as
let str: string = "this is a string";
// 尖括号<>
let strLength1: number = (<string>str).length;
// as
let strLength2: number = (str as string).length;
复制代码
这里建议你们统一使用 as 语法,由于在 React 的 tsx 语法中必须使用 as。
当 TypeScript
不肯定一个联合类型的变量究竟是哪一个类型的时候,咱们只能访问此联合类型的全部类型里共有的属性或方法,如
function getLength(str: string | number): number {
// 错误,由于 number 类型没有 length 方法
return str.length;
}
// Property 'length' does not exist on type 'number'.ts(2339)
复制代码
为此,咱们须要用到类型断言
function getLength(str: string | number): number {
if (typeof str === 'number') {
// 此时将 str 的类型判断为 number 类型,可使用 number 类型的属性和方法
return (str as number).toString().length
} else if (typeof str === 'string') {
// 此时将 str 的类型判断为 string 类型,可使用 string 类型的属性和方法
return (str as string).length
} else {
throw 'error'
}
}
复制代码
恭喜你👏看到这里,TypeScript
最为须要掌握的基础你已经学完了,不太重头戏才刚刚开始😅
枚举是 TypeScript
中,对 JavaScript
标准数据类型的补充,例如一个 Http
包含哪些状态,一星期是从星期一到星期日等,咱们均可以用枚举表达,若是你用事后端的编程语言,应该对枚举不陌生。
不一样于对象定义的 key-value 中,只能经过 key 去访问 value 的值,在 enum 中,既能够经过 key 访问 value 的值,也能够经过 value 访问 key 的值。
枚举用 enum
关键字来定义
enum Person {
Male,
Female
}
复制代码
上面的代码编译以下
var Person;
(function (Person) {
Person[Person["Male"] = 0] = "Male";
Person[Person["Female"] = 1] = "Female";
})(Person || (Person = {}));
复制代码
可见,若没给枚举的成员赋值,那么会默认从 0
开始递增。
固然咱们也能够给枚举手动赋值
enum Person {
Male = 7,
Female // 此时 Female 会为 8
}
// 由于未赋值的枚举会接着上一个枚举项递增,所以此时 Female 会为 8
复制代码
上面提到的都是常数项,其实枚举还有一个类型叫 计算所得项
enum Person {
Male, // 常数项
Female = "female".length // 计算所得项
}
// 若是计算所得项后面是没有赋值的项,则会报错
// 错误
enum Person {
Male = "man".length,
Female
}
// 正确
enum Person {
Male = "man".length,
Female = 8
}
复制代码
常数枚举是用 const enum
定义的,常数枚举在编译的阶段会被删除,既在编译后的文件不存在编译后的常数枚举,且不能包含计算成员
// 正确
const enum Person {
Male,
Female
}
// 错误
const enum Person2 {
Male,
Female = "female".length
}
let person = [Person.Male, Person.Female]
复制代码
编译结果
var person = [0 /* Male */, 1 /* Female */];
复制代码
外部枚举是用 declare enum
定义的,外部枚举用来描述已经存在的枚举类型的形状。
declare enum Person {
Male,
Female
}
let person = [Person.Male, Person.Female]
复制代码
编译结果
var person = [Person.Male, Person.Female];
复制代码
元组合并了不一样类型的对象,须要以元组所定义的顺序预约数据类型。
下面举个简单的例子
let tuple1: [string, number];
// 正确
tuple1 = ["one", 2];
// 错误
tuple1 = ["one", "two"];
tuple1 = [1, 2];
tuple1 = ["one", 2, 3]; // 未在元组中定义
tuple1 = [true, 2] // 元组在index为0中只接受string类型
复制代码
还记得上文数组泛型的定义吗
let arr1: Array<number> = [1, 2, 3, 4, 5];
复制代码
这里的 Array<number>
只容许数组的每一项都要为 number
类型,但有的时候,咱们但愿返回值的类型与传入参数的类型是相同的,所以有了泛型。
这里是官方文档的例子:
function identity<T>(arg: T): T {
return arg;
}
复制代码
这里的 identity
函数就被称为泛型,由于它能够用于多个类型,咱们给 identity
添加了类型变量 T
。 T
会帮助咱们捕获用户传入的类型(好比:number),以后咱们就可使用这个类型。 以后咱们再次使用了 T
当作返回值类型。
定义了泛型以后,咱们能够这样使用
let output1 = identity<string>("myString");
// 类型推论。编译器会根据传入的参数自动地帮助咱们肯定 T 的类型
// 此时的 T 为 string 类型
let output2 = identity("myString");
// 此时的 T 为 number 类型
let out2 = identity(123)
// 此时的 T 为 boolean 类型
let out3 = identity(true)
复制代码
若是咱们想打印 arg
的长度,会发现编译器报错。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length return arg; } 复制代码
这是由于泛型 T
是默认把 arg
参数看成是任意或者全部类型,而 number
类型没有 length
属性,因此会报错。
若是咱们传入数字数组,将返回一个数字数组,由于此时 T
的的类型为 number
。 这可让咱们把泛型变量 T
当作类型的一部分使用,而不是整个类型,增长了灵活性。
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // arg
return arg;
}
复制代码
咱们把上面例子的对象字面量换位接口
interface Iidentity {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: Iidentity = identity;
复制代码
与泛型接口相似,泛型也能够用于类的类型定义中
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
复制代码
看到这里,你还敢说不了解 TypeScript
吗?若是还不了解,那必定是你还没给这篇文章点赞👍👍👍!!!
看得不过瘾?这里还有别的文章