大致印象
- TypeScript 增加了静态类型、类、模块、接口和类型注解.
- TypeScript 可用于开发大型的应用
- TypeScript 使用类型和接口等概念来描述正在使用的数据,这使开发人员能够快速检测错误并调试应用程序
- TypeScript 中的数据要求带有明确的类型
- TypeScript 为函数提供了缺省参数值。
- TypeScript 引入了 JavaScript 中没有的“类”概念。
- TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。
一.快速上手
安装typescript
npm install -g typescript
编译代码
[踩坑]: 报错“此系统禁止运行脚本”
[解决方案]: 以管理员身份打开powershell, 键入:set-ExecutionPolicy RemoteSigned, 再选择Y/A即可
tsc demo.ts
类型注解
TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。
//TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解
function greeter(person: string) {
return "Hello, " + person;
}
let user = [0, 1, 2];
console.log(greeter(user));
// Argument of type 'number[]' is not assignable to parameter of type 'string'.
接口
接口是利用关键字interface声明一个函数的传入参数应该是什么样子的“组合类型”。
当传入的参数对象中的个别参数少了/类型不匹配,则报错;参数多了没关系。
interface Person {
firstName: string;
lastName: string;
}
function greeter(person: Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = { firstName: "Jane"};
let user = { firstName: "Jane", lastName: 'jiege'};
let user = { firstName: "Jane", lastName: 'jiege', age: 28};
console.log(greeter(user));
类
typescript支持基于类的面向对象编程(TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写)
//该类带有一个构造函数和一些公共字段
class Student {
fullName: string; //构造函数的公共字段需注明类型
//构造函数的参数,需注明暴露程度(public,private等等)
//在构造函数的参数上使用public等同于创建了同名的成员变量
constructor(public firstName, public middleInitial, public lastName) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
//类和接口可以一起工作,开发者可自行决定抽象的级别
interface Person {
firstName: string;
lastName: string;
}
function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}
let user = new Student("Jane", "M.", "User");
console.log(greeter(user));
二. Typescript和React的配合
因为create-react-app中已经默认内嵌了typescript,因此该github项目已经archived了
三. Typescript和Express、MongoDB的配合
四. Typescript和微信小程序的配合(Taro支持选择Typescript)
五. Typescript基础知识
基本类型
//boolean
let isDone: boolean = false;
//number
//同js,ts中的所有数字都是浮点数
//ts支持十进制、十六进制、二进制、八进制
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
//string
let name: string = `my name is ${name}`;
//Array
//声明1
//当类型后面跟着的是[]时候,表示左边的变量是一个数组,数组元素类型是该类型
let name: string[] = ['zhang', 'jie'];
//声明2
//数组泛型,尖括号里面是元素的类型
let names:Array<string> = ['zhang', 'jie'];
//元组
//当一个数组的元素的数量和类型确定时,其中各元素的类型不必相同可以声明一个元组类型
let x: [string, number, boolean] = ['stevekeol', 28, true];
//当访问一个越界的元素,会使用联合类型替代
//当访问一个已知索引的元素,会得到正确的类型
//enum
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
console.log(c); //c返回的是序列号1(默认情况下,元素编号从0开始)
//可以手动赋值
enum Color {Red = 1, Green, Blue}
//可以全部手动赋值
enum Color {Red = 1, Green = 2, Blue = 4}
//枚举类型最大的好处时可以根据枚举的值得到它的名字
let colorName: string = Color[2];
//Any
//表示任何类型都可以
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
//当你只知道数组一部分元素类型时
let list: any[] = [1, true, "free"];
//void
//表示任何类型都没有
//表示该函数没有返回值
function warnUser(): void {
console.log('You are my girl');
}
//undefined, null
//undefined, null是所有类型的子类型,即可以赋值给其它类型(除非指定了--strictNullChecks标记)
//也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined
//鼓励尽可能多的使用--strictNullChecks
//never类型表示的是那些永不存在的值的类型
//object
//表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
//类型断言
//比如对于any类型,有时候你明确知道它具体是什么类型
let str: any = 'You are my girl'
//第一种类型断言
let leng: number = (<string>str).length;
//第二种类型断言
let leng: number = (str as string).length;
接口
// interface的基本用法
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
//interface的可选属性
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
//只读属性
interface Point {
readonly x: number;
readonly y: number;
}
//ReadonlyArray声明数组时,可以确保数组创建后不会被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
//readonly和const的区别是,如果你想声明一个属性,则用readonly,如果想声明一个变量,则用const
//函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的
//调用签名
interface SearchFunc {
(source: string, substring: string): boolean;
}
let mySearch: SearchFunc = function(source: string, substring: string) {
return source.search(substring) > -1
}
//可索引的类型
//可以用索引签名,描述对象索引的类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
//TS支持数字和字符串两种索引,可以同时使用两种索引,但是需要注意,数字索引的返回值的类型必须是字符串索引返回的类型的子类型。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: number]: Animal; // error
[x: string]: Dog;
}
//返回的类型要跟索引签名中的返回类型一致 ??????????????????????
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
//将索引签名设置为只读,防止给索引签名赋值
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
//类的接口 ????????????????????????
interface ClockInterface {
currentTime: Date;
setTime(d: Date); //在接口中描述一个方法,在类中实现它
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
//类的静态部分和实例部分的区别 ????????????????????????????????
//接口描述了类的公共部分,而不是公共和私有两部分
//定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
//继承接口
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let test = <Square>{};
test.color = 'green';
test.sideLength = 28;
//单个接口继承多个接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{}; //类型断言(即说明更为详尽的类型)
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
//混合类型
//如:一个对象可以同时做为函数和对象使用,并带有额外的属性
interface Counter {
(start: number): string; //接口中声明入参和返回值的类型
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
//接口继承类
//当接口继承了一个类类型时,它会继承类的成员但不包括其实现
//当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
类
//类
//传统的JS使用函数和基于原型的继承来创建可重用的组件,面向对象的开发者而言,基于类的继承且对象是由类构建出来的
//ES6支持使用基于类的面向对象的方式
//TS也支持使用基于类的面向对象的方式(TS中可以使用ES6吗?可以的话,继承这一块代码层面怎么实现)
class Greeter {
greeting: string; //属性
constructor(message: string) { //构造方法
this.greeting = message;
}
greet() { //方法
return `Hello, ${this.greeting}`;
}
}
let greeting = new Greeter('stevekeol');
//继承
class Animal { //超类(基类)
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal { //子类(衍生类)
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
//复杂继承
//派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们 一定要调用 super()
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) { //子类中重写父类的方法
console.log('Slithering...');
super.move(distanceInMeters); //此处调用父类的同名方法,适用于局部调整父类的方法
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log('Slithering...');
super.move(distanceInMeters);
}
}
let sam = new Snake('stevekeol is cool');
//和sam实例构造方式的区别是:tom实例只能使用Animal中的方法,不能使用Horse中自定义的方法
//即使 tom被声明为 Animal类型,但因为它的值是 Horse,调用 tom.move(34)时,它会调用 Horse里重写的方法
let tom: Animal = new Horse('zhangjie niubi');
sam.move();
tom.move(34);
//private修饰符
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
//如果一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,这两个类型是兼容的。
//Animal和 Rhino共享了来自 Animal里的私有成员定义 private name: string,因此它们是兼容的.
// 对于 protected成员也使用这个规则
animal = rhino;
animal = employee; // 错误: Animal 与 Employee 不兼容.
//protected修饰符
//protected修饰的变量,在其衍生类中也可访问使用,但是其实例中早已经不可用了
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误
//构造函数使用protected修饰符
//则该类虽然能被继承,但是不能在包含它的类外被实例化
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
//readonly修饰符
//只读属性必须在声明时或构造函数里被初始化
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
//参数属性
//即在构造函数的参数前面添加一个限定符,则声明和赋值会同时进行;
//使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样。
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
//存取器(有效的控制对对象成员的访问)
let password = 'zhangjie';
class Employee {
private _fullName: string;
get fullName(): string {
console.log('getting...');
return this._fullName;
}
set fullName(newName: string) {
if(password && password === 'zhangjie') {
console.log('setting');
this._fullName = newName;
} else {
console.log('Error: unAuthed');
}
}
}
let employee = new Employee();
employee.fullName = 'stevekeol';
if(employee.fullName) {
console.log(employee.fullName);
}
//类的静态属性(在类本身上而非实例上)
//每个实例都会用到的属性(但不是实例本身的属性)
//如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 '类名.'来访问静态属性
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale: number) {}
}
let grid1 = new Grid(1.0);
let grid2 = new Grid(5.0);
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
//抽象类(不直接实例化,都是作为基类衍生出派生类再实例化)
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log(`Department is ${this.name}`);
}
abstract printMeeting(): void; //抽象类中的抽象方法(仅定义方法签名但不包含方法体)不包含具体实现,且必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('YuZhou'); //派生类的构造函数中必须调用super()
}
printMeeting(): void {
console.log('hello world'); //派生类中实现抽象类的抽象方法
}
generateReports(): void {
console.log('generatied!');
}
}
let department: Department = new AccountingDepartment(); //可以创建一个对抽象类型的引用
department.printName();
department.printMeeting();
department.generateReports(); //错误: 方法在声明的抽象类中不存在
//高级用法 - 构造函数 ?????????????????????
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter = new Greeter();
console.log(greeter1.greet());
//取Greeter的类型而非实例的类型,即构造函数的类型,它包含了类所有的静态成员和构造函数
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
//高级技巧 - 类当接口使用
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
函数
//函数的完整类型
let myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; };
//函数的简写类型
function add(x: number, y: number): number {
return x + y;
}
//可选参数
//1. '?'必须跟在参数名称后面
//2. 可选参数必须跟在必须参数后面
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
//默认参数
function buildName(firstName: string, lastName = "Smith") {
//...
}
//剩余参数
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
// this的绑定
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
//此处返回箭头函数,直接在函数返回时就将this绑定,而不是调用时再绑定
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
console.log(this);
return {suit: this.suits[pickedSuit], card: pickedCard % 13}; //返回一个对象时的写法
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
//this参数 --- 重点学习
//this参数是个假的参数,它出现在参数列表的最前面
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return {suit: this.suits[pickedSuit], card: pickedCard % 13};
}
}
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
//带this的回调函数 -------?????????????????????
interface UIElement {
addEventListener(onclick: (this: void, e: Event) => void): void;
}
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!因为addClickListener要求函数带有this:void
//迫不得已的解决办法(箭头函数不会捕获this,所以可以传给期望this: void的函数)
//每个 Handler对象都会创建一个箭头函数。
//方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
//重载
//为同一个函数提供多个函数类型定义来进行函数重载
//在定义重载的时候,一定要把最精确的定义放在最前面
//function pickCard(x): any并不是重载列表的一部分
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number; //对象数组的类型声明的写法
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);
泛型
function identify(arg: number): number {
return arg;
}
function identify(arg: number): number {
return arg;
}
//多个泛型类型的使用
function identities<T, U>(arg1: T, arg2: U): [T, U] {
return [arg1, arg2];
}
//使用any类型时会丢失信息:输出和输入的类型不见得一致
function identify(arg: any): any {
return arg;
}
//使用类型变量(一种只表示类型而不是值的特殊变量)
function identify<T>(arg: T): T {
return arg;
}
//该版本的identify函数称之为泛型函数。既可以适用于多个类型,又可以保证不丢失类型的信息。
let output1 = identify<string>('mystring');
let output2 = identify('mystring'); //类型推论(更普遍的写法)
//使用泛型变量时注意泛型的通用性
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); //报错:T不见得一定有length属性
return arg;
}
//泛型数组的使用
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
//泛型数组的改写
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length);
return arg;
}
/************************************************/
//泛型函数的类型
function identify<T>(arg: T): T {
return arg;
}
//泛型类型参数在最前面
let myIdentify: <T>(arg: T) => T = identify;
//使用带有调用签名的对象字面量来定义泛型函数 ????????
let myIdentify: {<T>(arg: T): T} = identify;
//创建泛型接口,并使用
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identify<T>(arg: T): T {
return arg;
}
let myIdentify: GenericIdentityFn = identify;
//把非泛型函数签名作为泛型类型一部分????????????????
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
//创建泛型类,并使用
//类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = ""; //并没有什么限制只能使用number类型
myGenericNumber.add = (x, y) => x + y;
//泛型约束(一个类型参数,且它被另一个类型参数所约束)
function getProperty(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
//泛型里使用类类型(使用泛型创建工厂函数时,需要引用构造函数的类类型)
function create<T>(c: {new(): T; }): T {
return new c();
}
//泛型里使用类类型(使用原型属性推断并约束构造函数与类实例的关系)????????????????
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
枚举
//使用枚举我们可以定义一些带名字的常量
enum Direction {
Up = 1,
Down,
Left,
Right
}
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// 通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型
}
respond("Princess Caroline", Response.Yes)
//枚举成员的值可以是常量/计算出来的
//枚举-反向映射
enum Enum{
A,
B
}
let a = Enum.A;
console.log(Enum[a]);
类型推断
//使用这些表达式的类型来推断出一个最合适的通用类型,如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型
let zoo = [new Rhino(), new Elephant(), new Snake()];
声明合并
//接口合并
//当接口 A与后来的接口 A合并时,后面的接口具有更高的优先级
//每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。
//如果签名里有一个参数的类型是 单一的字符串字面量那它将会被提升到重载列表的最顶端。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
//合并命名空间
//合并之后,从其它命名空间合并进来的成员无法访问非导出成员
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
//合并命名空间和类
//合并结果是一个类并带有一个内部类(TypeScript使用这个功能去实现一些JavaScript里的设计模式)
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel { }
}
装饰器
启用实验性的装饰器特性, 需要:
//方案1:命令行
tsc --target ES5 --experimentalDecorators
//方案2:tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
启用实验性质的元数据,需要:
//先安装依赖包
npm i reflect-metadata --save
//方案1:命令行
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
//方案2:tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上
装饰器使用@expression形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。
//如@test装饰器
function test(target) {
//do something with target ...
}
//装饰器工厂
function color(value: string) {
return function (target) {
//do something with target
}
}
//装饰器组合
@f
@g
x
//由上至下依次对装饰器表达式求值
//求值的结果会被当作函数,由下至上依次调用,如同复合函数f(g(x))
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
类中不同声明上的装饰器将按以下规定的顺序应用:
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
- 参数装饰器应用到构造函数。
- 类装饰器应用到类。
//类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Obejct.seal(constructor.prototype);
}
//此时将密封该类的构造函数和原型
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this,greeting = message;
}
greet() {
return `hello, ${this.greeting}`;
}
}
//类装饰器 - 重载构造函数 ??????????????????
function classDecorator<T extends {new (...args: any[]): {}}>(constructor: T) {
return class extends constructor {
newProperty = 'new property';
hello = 'overide';
}
}
@classDecorator
class Greeter {
property = 'property'; //此处是用到类型推断了吗?
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter('world'));
// 方法装饰器
// 会被应用到方法的属性描述符上,用来监视、修改、替换方法定义
// 装饰器工厂返回的函数的三个参数的含义分别是?
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
}
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
//修改属性描述符的enumable属性
@enumerable(false)
greet() {
return `hello, ${this.greeting}`;
}
}
console.log(new Greeter('zhangjie').greet());
//访问器装饰器
//会被应用到访问器的属性描述符,用来监视,修改,替换访问器的定义
//因为装饰器应用到一个属性描述符时,联合了get和set访问器,而非分开声明的。因此TS不允许同时装饰一个成员的get和set访问器。
function configurable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
}
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
//属性装饰器
// 属性装饰器表达式会在运行时当做函数被调用,传入两个参数:
// 1. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
// 2. 成员的名字
// 反射API用于在运行时检测一个对象的元数据:实例的名字和类型,实现了哪些接口,属性的名字和类型,构造函数的参数名和类型等
//可用的元数据设计键有: design:type, design:paramtypes, design.returntype,分别是类型元数据,参数类型元数据,返回值类型元数据
import 'Reflect-metadata'; //该库需要额外安装
const formatMetadataKey = Symbol('format');
function fromat(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format('hello, %s')
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, 'greeting');
return formatString.replace('%s', this.greeting);
}
}
//或者
function logType(target : any, key : string) {
var t = Reflect.getMetadata("design:type", target, key);
console.log(`${key} type: ${t.name}`);
}
class Demo{
@logType // apply property decorator
public attr1 : string;
}
//输出:attr1 type: String
//参数装饰器(只能用来监视一个方法的参数是否被传入)
//参数装饰器应用在类的构造函数、方法声明中
//参数装饰器接受三个参数:
//1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
//2.成员的名字
//3.参数在函数参数列表中的索引
import "Reflect-metadata";
const requiredMetadataKey = Symbol('required');
function requried(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
}
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
//该装饰器将greet方法包裹在一个函数里在调用原先的函数前先验证函数参数
@Validate
greet(@required name: string) {
return `Hello ${name}, ${this.greeting}`;
}
}
高级类型
//交叉类型 - Intersection Types
//把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{}; //定义一个交叉类型的对象
for(let key in first) {
(<any>result)[key] = (<any>first)[key]; //这里为什么要用any?
}
for(let key in second) {
if(!result.hasOwnProperty(key)) {
(<any>result)[key] = (<any>second)[key];
}
}
return result;
}
class Person {
constructor(public name: string) {} //此处的public不能省(构造函数中的参数属性)
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
console.log('consoleLogger');
}
}
let person = extend(new Person('zhangjie'), new ConsoleLogger());
console.log(person);
console.log(person.name);
person.log();
//联合类型
//联合类型表示一个值可以是几种类型之一
function padLeft(value: string, padding: string | number) {
// ...
}
//如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
//typeof 类型保护
function padLeft(value: string, padding: string | number) {
//类型保护
//1.类型必须是number, string, boolean, symbol其中之一,否则TS不会将其视为类型保护
//2.类型保护只有两种形式:typeof v === 'typename' 或 typeof v !== 'typename'
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
//instanceof 类型保护
//将细化为instanceof右侧的构造函数的prototype的类型或者构造签名所犯回的联合类型
...
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder'
}
...
//类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
//字符串字面量类型
//实现类似枚举类型的字符串:三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if(easing === 'ease-in') {
console.log('ease-in');
} else if(easing === 'ease-out') {
console.log('ease-out');
} else if(easing === 'ease-in-out') {
console.log('ease-in-out');
} else {
//error
}
}
}
let button = new UIElement();
button.animate(0, 0, 'ease-in');
button.animate(0, 0, 'uneasy'); //error!
//字符串字面量类型还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
//多态的this类型
class BasicCalculator {
public constructor(protected value: number = 0) {}
public currentValue(): number {
return this.value;
}
public add(num: number): this {
this.value += num;
return this;
}
public mutiply(num: number): this {
this.value *= num;
return this;
}
//...
}
let test = new BasicCalculator(2)
.mutiply(5)
.add(1)
.currentValue();
console.log(test);
//BasicCalculator返回了this类型,因此派生类在继承的同时,还能保持接口的连贯性,即返回的this可以多台的指向ScientificCalculator
class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) { //此处为什么不对value声明类型?是用了类型推断么?
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
//...
}
let test2 = new ScientificCalculator(2)
.mutiply(5)
.sin() //不然此处mutiply()返回的没有sin()方法
.add(1)
.currentValue();
console.log(test2);
//索引类型
//常见的业务场景:从对象中选取属性的子集
function pluck(obj, names) {
return names.map(item => obj[key]);
}
//感悟这种声明方式
//(索引类型查询操作符)keyof T的结果为T上已知的公共属性名的联合('name' | 'age')
//(索引访问操作符)T[K]是一种类型!
function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
return names.map(item => obj[item]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'stevekeol',
age: 28
}
let strings: string[] = pluck(person, ['name']);
console.log(strings);
//索引类型和字符串索引签名 ?????????
//eyof和 T[K]与字符串索引签名进行交互。
//如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。
//并且 T[string]为索引签名的类型
interface Map<T> {
[key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number
//映射类型(新类型以相同的形式去转换旧类型中的每个属性),如可选/只读.
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
type Keys = 'option1' | 'option2'; //字符串字面量联合的Keys,包含了要迭代的属性名的集合
type Flags = {
[K in Keys]: boolean; //类型变量K,会依次绑定到每个属性上
}
//等同于
type Flags = {
option1: boolean;
option2: boolean;
}
//真正的应用中,(keyof和索引访问类型)常常会基于已存在的一些类型,按照一定的方式转换字段。
//(属性列表是keyof Person; 结果类型是Person[P])
//编辑器在添加任何新属性之前可以拷贝所有存在的属性修饰符(只读/可选)
type NullablePerson = { [P in keyof Person]: Person[P] | null };
type PartialPerson = { [P in keyof Person]?: Person[P] };
//进阶为通用版本
type NullablePerson<T> = { [P in keyof T]: T[P] | null };
type PartialPerson<T> = { [P in keyof T]?: T[P] };
//其它例子 ???????????
type Proxy<T> = {
get(): T;
set(value: T): void;
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
}
function proxify<T>(obj: T): Proxify<T> {
//... wrap proxies
}
let proxyProps = proxify(props);
//TS标准库(Readonly,Partial,Pick,Record...)
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
//?
type Record<K extends string, T> = {
[P in K]: T;
}
//Readonly,Partial,Pick都是同态的(映射只作用于T的属性)
//Record不是同态的,因为它不需要输入类型来拷贝属性。
//非同态类型本质上会创建新的属性,因为它们不会从他处拷贝属性修饰符。
type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>
//由映射类型进行推断
function unproxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}
let originalProps = unproxify(proxyProps);










网友评论