安装
npm install -g typescript //以上命令为全局环境下安装tsc命令,安装完成之后,我们就可以在任何地方执行 `tsc` 命令了。
tsc -v //查看安装的tsc版本名
tsc 文件名.ts //编译一个 TypeScript 文件
注意事项
- 我们约定使用 TypeScript 编写的文件以
.ts为后缀,用 TypeScript 编写 React 时,以.tsx为后缀 - 使用
:指定变量的类型,:的前后有没有空格都可以 - TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错,但还是会生成编译文件,且任可以这个编译后的文件
数据类型
1.布尔值
在Typescript使用boolean来定义布尔值类型,直接写true/false 或 使用Boolean()创建可以返回符合Typescript的boolean值
let bool: boolean = false;
let bool1: boolean = Boolean(false);
使用构造函数Boolean创建的对象不是布尔值 !!!!!!! 使用new Boolean()返回的是对象
let bool: boolean = new Boolean(false);
tsc.ts:1:5 - error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
1 let bool: boolean = new Boolean(false);
2.数值
TypeScript中使用number定义数值类型,会将二进制和八进制数字编译为十进制数字
let num1: number = 10;//十进制数
let num2: number = 0xff;//十六进制数
let num3: number = 0o07;//八进制数
let num4: number = 0b1010;//二进制数
let num5: number = NaN;//NaN
let num6: number = Infinity;//无限制
编译结果:
var num1 = 10; //十进制数
var num2 = 0xff; //十六进制数
var num3 = 7; //八进制数
var num4 = 10; //二进制数
var num5 = NaN; //NaN
var num6 = Infinity; //无限制
3.字符串
在Typescript使用string来定义布尔值类型,使用new String()创建的是对象,不是字符串
let str1: string = 'str1';//直接写
let str2: string = String('str2');//使用String()创建字符串
let str3: string = `我是 ${str1} 和 ${str2} 的合集`; //模板字符串
4.空值
针对于JavaScript的函数,在JavaScript中没有空值的概念,TypeScript中使用void表示没有任何返回值的函数,
声明一个void的变量没有什么用,因为你只能给他赋值为null 或 undefined,后期再赋其他类型的值时编译会报错;
//空值
function f(): void {
console.log('我是void函数');
}
5.Null和Undefined
在Typescript使用null和undefined来这两个数据类型,与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量
//null 和 undefined
let u1: null = null;
u1 = 123; //这样重赋值编译会报错
//以下写法都不会报错
let u2: number = null;
u2 = 123;//这样重赋值编译不会报错
let u3:null = undefined;
let u4:undefined = undefined;
任意值
任意值(Any)用来表示允许赋值为任意类型。
如果是一个普通类型,在赋值过程中改变类型是不被允许的,但如果是 any 类型,则允许被赋值为任意类型。
声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
//任意值 any
let any1: any ;
let any2: any = '我是any 2';
//任意值重赋值
any2 = 0; any2 = true;
//任意值的属性和方法
console.log(any2.name);
console.log(any2.setName());
//变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let any3 ;
any3 = 100; any3 = '123';
//下面这种写法在tsc 编译时会报错(tsc 类型推论)
let any4 = 0;
any4 = '123';
类型推论
后续使用tsc指代Typescript
在tsc中,如果定义变量时有复制,tsc 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论;
//类型推论
let any4 = 0;
any4 = '123';
tsc.ts:55:1 - error TS2322: Type 'string' is not assignable to type 'number'.
55 any4 = '123';
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
let any3 ;
any3 = 100; any3 = '123';
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型
let uType1: number | string | boolean;
uType1 = '23'; uType1 = 100; uType1 = false;
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
function getLength(str: string | number){
return str.length
}
tsc.ts:62:16 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
62 return str.length
这里str在调用函数传入值之前,都不确定str的类型, length 不是 string 和 number 的共有属性,所以会报错;
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
let uType2: number | string = '100';
console.log(uType2.length);//3
uType2 = 7;
console.log(uType2.length);
tsc.ts:68:20 - error TS2339: Property 'length' does not exist on type 'number'.
68 console.log(uType2.length);
上例中, uType2 被推断成了 string,访问它的 length 属性不会报错,而之后uType2 赋值为7,被推断成了 number,访问它的 length 属性时就报错了;
对象的类型————接口
接口概念:在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)
//对象类型--接口
interface Person {
name: string,
age: number
}
let liLei: Person = {
name:'LiLei',
age:18,
}
定义的变量比接口少了一些属性或多一些属性是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致。
//对象类型--接口
interface Person {
name: string,
age: number
}
//少一个属性
let liLei: Person = {
name:'LiLei',
}
// tsc.ts:80:5 - error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
// 80 let liLei: Person = {
// tsc.ts:78:5
// 78 age: number
// 'age' is declared here.
//多一个属性
let liLei: Person = {
name:'LiLei',
age: 22,
gender:1
}
tsc.ts:83:5 - error TS2322: Type '{ name: string; age: number; gender: number; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
83 gender:1
Found 1 error in tsc.ts:83
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性,在属性后面加上?,这时仍然不允许添加未定义的属性
interface Person {
name: string,
age: number
gender?:number, //可选属性,这个属性可存在也可不存
}
任意属性
有时候我们希望一个接口允许有任意的属性,使用 [propName: string]:类型;定义任意属性,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集!!!!!
interface Person {
name: string,
age: number
gender?:number, //可选属性,这个属性可存在也可不存
[propName:string]:any //任意属性
}
let liLei: Person = {
name:'LiLei',
age: 22,
gender:1,
id:10,
}
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性,
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
interface Person {
name: string,
age: number
gender?:number, //可选属性,这个属性可存在也可不存
[propName:string]:any //任意属性
readonly id:number,//只读属性
}
let liLei: Person = {
name:'LiLei',
age: 22,
gender:1,
id:10,
};
liLei.id = 11;
tsc.ts:89:7 - error TS2540: Cannot assign to 'id' because it is a read-only property.
89 liLei.id = 11;
Found 1 error in tsc.ts:89
数组类型
在tsc中表示数组类型的方式比较灵活
1.类型+方括号 表示法
2.数组泛型
Array<elemType>3.接口表示
//数组的类型
let arr1: number[] = [1,2,3,4,5];//类型+[] 此方法中的数组元素不允许出现其他类型
let arr2: Array<number> = [1,2,3,4,5]; //数组泛型 Array<elemType>
interface arrInit {
[propName: number]: number;
}
let arr3: arrInit = [1,2,3,4,5]; //接口表示法
let arrAny: Array<any> = [1,'2','li'];//any 表示数组中允许出现任意类型
类数组
类数组(Array-like Object)不是数组类型,比如 arguments,应该用接口表示法来描述它
function sum() {
let args:{
[index:number]:number,
length:number,
callee:Function,
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性。
事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等
函数的类型
定义函数有两种方式————函数声明、函数表达式
函数声明
一个函数有输入和输出,要在 tsc 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单
//函数的类型
function total(x: number, y: number): number {
return x + y;
}
输入多余的(或者少于要求的)参数,是不被允许的
total(1,2,3);
tsc.ts:116:11 - error TS2554: Expected 2 arguments, but got 3.
116 total(1,2,3);
函数表达式
1.手动给 total1 添加类型
let total1:(x:number,y:number) => number = function (x:number,y:number):number {
return x+y;
}
注意不要混淆了 tsc 中的
=>和 ES6 中的=>。在 tsc 的类型定义中,
=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。在 ES6 中,
=>叫做箭头函数
2.接口定义函数的形状
let total2:{(x:number,y:number):number} = function (x:number,y:number):number {
return x+y;
}
可选参数
输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?与接口中的可选属性类似,我们用 ? 表示可选的参数
需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了
function total3(x:number ,y?:number):number {
if(y) return x+y;
else return x;
}
参数默认值
在 ES6 中,我们允许给函数的参数添加默认值,tsc 会将添加了默认值的参数识别为可选参数
此时就不受「可选参数必须接在必需参数后面」的限制了
function total4(x: number = 10, y: number) {
return x + y;
}
剩余参数
ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数)
//剩余参数
function residue1(num,...rest) {
//代码块
}
residue1(1,2,3,4);
事实上,rest 是一个数组。所以我们可以用数组的类型来定义它
function residue2(num:number,...rest:Array<number>) {
//代码块
}
注意,rest 参数只能是最后一个参数
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理 如将数字或字符串反转输出
//重载
function reserve(value) {
if (typeof value == 'number') {
return Number(value.toString().split('').reverse().join());
}
else if (typeof value == 'string') {
return value.split('').reverse().join('');
}
}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reserve 的函数类型:
function reserve1(value: string): string;
function reserve1(value: number): number;
function reserve1(value: string | number): string | number | void {
if (typeof value == 'number') {
return Number(value.toString().split('').reverse().join());
} else if (typeof value == 'string') {
return value.split('').reverse().join('');
}
}
上例中,我们重复定义了多次函数 reserve1,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
作用:手动指定一个值的类型
语法: 1.值
as类型2.<类型>值
统一使用
as语法,因为在tsx(React 的 jsx 语法的 ts 版)语法中<类型>存在异议,在 ts 中除了表示类型断言之外,也可能是表示一个泛型
用途:
1.将一个联合类型断言为其中一个类型,之前再“联合类型”中提到,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法,而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,此时可以使用类型断言
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
注意:这个只是解决tsc编译报错问题,无法避免运行时报错
2.将一个父类断言为更加具体的子类
如果是父类与子类的继承关系,建议使用instanceof,而不是使用断言
如果是接口类型的继承,建议还是使用断言
3.将任何一个类型断言为 any
window.foo = 1;
// tsc.ts:188:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
// 188 window.foo = 1;
我们给将 window 上添加一个属性 foo,但 tsc 编译时会报错,提示我们 window 上不存在 foo 属性。
此时我们可以使用 as any 临时将 window 断言为 any 类型:
(window as any).foo = 1;
在 any 类型的变量上,访问任何属性都是允许的。
需要注意的是,将一个变量断言为 any 可以说是解决 tsc 中类型问题的最后一个手段。
它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any。
4.将 any 断言为一个具体的类型
在日常的开发中,我们不可避免的需要处理 any 类型的变量,我们也可以选择改进它,通过类型断言及时的把 any 断言为精确的类型,使我们的代码向着高可维护性的目标发展
去掉any的最优解——————泛型
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
这个例子中getCacheData返回的是一个any类型的值,我们现在就用泛型去掉any:
function getCacheData<T>(key: string): T {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData<Cat>('tom');
tom.run()
声明文件
全局变量的声明文件主要有以下几种语法:
declare var/let/const声明全局变量declare function声明全局方法declare class声明全局类declare enum声明全局枚举类型declare namespace声明(含有子属性的)全局对象interface和type声明全局类型
npm 包的声明文件主要有以下几种语法:
export导出变量export namespace导出(含有子属性的)对象export defaultES6 默认导出export =commonjs 导出模块
内置对象
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准,它们可以直接在 tsc 中当做定义好了的类型。
- 常用内置对象:String、Number、Symbol、Boolean、Array、Error、Date、RegExp、Function、Set等
- 常用DOM和BOM对象:Document、HTMLElement、Event、NodeList等
let bool:Boolean = new Boolean(1);
let str:String = new String(123);
let date:Date = new Date();
//DOM和BOM
let body:HTMLElement = document.body;
let divList:NodeList = document.querySelectorAll('div');
document.addEventListener('click',function (e:MouseEvent) {
//……
})
tsc核心库的定义中不包含 Node.js 部分,如果想用 tsc 写 Node.js,则需要引入第三方声明文件:
npm install @types/node --save-dev