资料来源
http://ts.xcatliu.com/
安装Typescript
1.安装
代码语言:javascript复制//安装
npm install -g typescript
//编译
tsc hello.ts
//初始化
tsc --init
TypeScript 最大的优势之一便是增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。
基础入门
Typescript可以理解为带静态类型的Javascript;
小贴士
Ts同样有着先声明后赋值、声明的同时赋值这些操作;
1.原始数据类型
- 布尔值,boolean
- 数值,number
- 字符串,string
- 空值,void 表示没有任何返回值的函数
- Null 和 Undefined
/* 先声明后使用 */
let a: string;
a = "aaa";
/*声明的同时赋值*/
let b: string = "aaa";
/*类型推论*/
let c = false;
c = 1; //报错,类型推论得出c为布尔值
类型推论
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候有赋值,类型就会被推断为这个值得类型;
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
2.任意值(any)
如果是一个普通类型,在赋值过程中改变类型是不被允许的:
代码语言:javascript复制let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
但如果是 any 类型,则允许被赋值为任意类型。
代码语言:javascript复制let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
3.联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型。
代码语言:javascript复制let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
代码语言:javascript复制function getLength(something: string | number): number {
return something.length;//访问length报错,以为number没有length属性
return something.toString; //不报错,toString是共有属性
}
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型;
4.对象的类型—接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
代码语言:javascript复制interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
定义的变量比接口少、多一些属性是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致。
4.1 可选属性
可选属性的含义是该属性可以不存在,但是仍然不允许添加未定义的属性。
代码语言:javascript复制interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
4.2 任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
代码语言:javascript复制interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
使用 [propName: string]
定义了任意属性取 string
类型的值。
注意
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型
4.3 只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
代码语言:javascript复制interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
5.数组的类型
代码语言:javascript复制//最简单的方法是使用「类型 方括号」来表示数组
let fibonacci: number[] = [1, 1, 2, 3, 5];
//数组泛型(Array Generic) Array<elemType>
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
//接口也可以用来描述数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//任意类型的数组
ley arr:any=[1,"2",false];
6.函数的类型
在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):
代码语言:javascript复制// 函数声明(Function Declaration)
function sum(x, y) {
return x y;
}
// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x y;
};
一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
代码语言:javascript复制function sum(x: number, y: number): number {
return x y;
}
函数表达式定义如下:
代码语言:javascript复制let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x y;
};
注意
输入多余的(或者少于要求的)参数,是不被允许的:
我们也可以使用接口的方式来定义一个函数需要符合的形状:
代码语言:javascript复制interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
注意
js中的数组、函数同样都是对象,所以接口定义类型同样适它们
6.1 可选参数
与接口中的可选属性类似,我们用 ? 表示可选的参数:
代码语言:javascript复制function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName ' ' lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
注意
可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了
6.2 参数默认值
在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受「可选参数必须接在必需参数后面」的限制了:
代码语言:javascript复制function buildName(firstName: string, lastName: string = 'Cat') {
return firstName ' ' lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
6.3 剩余参数
ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数):
代码语言:javascript复制function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a: any[] = [];
push(a, 1, 2, 3);
事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
代码语言:javascript复制function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
注意,rest 参数只能是最后一个参数,关于 rest 参数,可以参考 ES6 中的 rest 参数。
6.4 重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
代码语言:javascript复制function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
7.类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
代码语言:javascript复制值 as 类型 / <类型>值
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
代码语言:javascript复制interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
8.声明文件
代码语言:javascript复制//定义了全局变量 jQuery 的类型
declare var jQuery: (selector: string) => any;
jQuery('#foo');
参考:http://ts.xcatliu.com/basics/declaration-files.html
进阶
1.类型别名
代码语言:javascript复制type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
2.字符串字面量类型
代码语言:javascript复制type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'
3.元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
代码语言:javascript复制//定义一对值分别为 string 和 number 的元组
let tom: [string, number] = ['Tom', 25];
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型:
代码语言:javascript复制let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
4.枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
代码语言:javascript复制enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
编译后:
代码语言:javascript复制var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
5.类与接口
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
6.泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
代码语言:javascript复制function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i ) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
定义泛型的时候,可以一次定义多个类型参数:
代码语言:javascript复制function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
具体概念可以参考java的泛型
7.声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型:
代码语言:javascript复制function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
内置对象
Typescript内置了所有js、dom对象,核心库类型定义文件:https://github.com/Microsoft/TypeScript/tree/main/src/lib
命名空间
使用 namespace 关键字定义命名空间,可以在命名空间内部定义变量、函数表达式、函数声明、接口和 类等值。
为了让命名空间外部可以访问命名空间内部声明的值和类型,使用 export 关键字导出指定的值和类型;
代码语言:javascript复制namespace Tools {
var count = 0
//导出 add
export var add = function (x: number, y: number) {
return x y
}
}
引用外部文件的命名空间内的成员时,需要export指定命名空间
代码语言:javascript复制exprot namespace {
let a:any=1;
export {a}
}
相关总结
类型库:https://github.com/DefinitelyTyped/DefinitelyTyped/
TypeScript 核心库的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在 TypeScript 中的。
Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:npm install @types/node --save-dev
类型别名用来给一个类型起个新名字。type Name = string;
字符串字面量类型,type EventNames = 'click' | 'scroll' | 'mousemove'; 使用EventNames 就等于之后的三个类型;
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。let tom: [string, number] = ['Tom', 25];
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等,也可以给枚举项手动赋值:。
代码语言:javascript复制enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
代码语言:javascript复制function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i ) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
代码检查:http://ts.xcatliu.com/engineering/lint.html
编译选项:http://ts.xcatliu.com/engineering/compiler-options.html
Es6 类
1.属性和方法
代码语言:javascript复制class Animal {
public name;
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
2.类的继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法
代码语言:javascript复制class Cat extends Animal {
constructor(name) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi() {
return 'Meow, ' super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
3.存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:
代码语言:javascript复制class Animal {
constructor(name) {
this.name = name;
}
get name() {
return 'Jack';
}
set name(value) {
console.log('setter: ' value);
}
}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
4.静态方法
使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
代码语言:javascript复制class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
Es7 类
Es7相关提案在Typescript中已实现。
1.实例属性
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
代码语言:javascript复制class Animal {
name = 'Jack';
constructor() {
// ...
}
}
let a = new Animal();
console.log(a.name); // Jack
2.静态属性
ES7 提案中,可以使用 static 定义一个静态属性:
代码语言:javascript复制class Animal {
static num = 42;
constructor() {
// ...
}
}
console.log(Animal.num); // 42
TypeScript 中类的用法
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
1.抽象类
abstract 用于定义抽象类和其中的抽象方法
代码语言:javascript复制abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack');
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
tsconfig.json
如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。
代码语言:javascript复制{
// ...
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
},
"exclude": [
"src/lib" // 排除src目录下的lib文件夹下的文件不会编译
],
"files": [
// 指定编译文件是src目录下的leo.ts文件
"scr/leo.ts"
],
"include": [
// "scr" // 会编译src目录下的所有文件,包括子目录
// "scr/*" // 只会编译scr一级目录下的文件
"scr/*/*" // 只会编译scr二级目录下的文件
]
}
相关参考
https://zhuanlan.zhihu.com/p/145210784
声明文件
1.相关语法
- declare var 声明全局变量
- declare function 声明全局方法
- declare class 声明全局类
- declare enum 声明全局枚举类型
- declare namespace 声明(含有子属性的)全局对象
- interface 和 type 声明全局类型
- export 导出变量
- export namespace 导出(含有子属性的)对象
- export default ES6 默认导出
- export = commonjs 导出模块
- export as namespace UMD 库声明全局变量
- declare global 扩展全局变量
- declare module 扩展模块
- /// 三斜线指令
2.三斜线指令
三斜线指令也是 ts 在早期版本中为了描述模块之间的依赖关系而创造的语法。随着 ES6 的广泛应用,现在已经不建议再使用 ts 中的三斜线指令来声明模块之间的依赖关系了。
代码语言:javascript复制/// <reference path="..." /> #按路径引入
/// <reference types="..." /> #按包名引入
类似于声明文件中的 import,它可以用来导入另一个声明文件。与 import 的区别是,当且仅当在以下几个场景下,我们才需要使用三斜线指令替代 import:
- 当我们在书写一个全局变量的声明文件时,在全局变量的声明文件中,是不允许出现 import, export 关键字的。一旦出现了,那么他就会被视为一个 npm 包或 UMD 库,就不再是全局变量的声明文件了。故当我们在书写一个全局变量的声明文件时,如果需要引用另一个库的类型,那么就必须用三斜线指令
- 当我们需要依赖一个全局变量的声明文件时,当我们需要依赖一个全局变量的声明文件时,由于全局变量不支持通过 import 导入,当也就必须使用三斜线指令来引入
- 拆分声明文件,当我们的全局变量的声明文件太大时,可以通过拆分为多个文件,然后在一个入口文件中将它们一一引入,来提高代码的可维护性。
3.d.ts文件
ts 会解析项目中所有的 *.ts 文件、 .d.ts 结尾的文件。所以全局类型声明放在.d.ts中,可直接使用,不需要手动去引入。非全局则需要引入
问题总结
1.通过下标获取对象属性
参考:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types
ts直接通过属性名下标访问对象属性会报错,需要通过keyof处理。