TypeScript-Knowlege
一. 基础类型
1.基础类型
布尔值: let isDone:boolean=false;
数字: let height:number:123/0xf00d; //TypeScript里的所有数字都是浮点数
let age: number = 37;
字符串:let name: string = "bob";
let sentence: string = `Hello ${ name }` //相当于"Hello " + name
数组: let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3]; //由number类型元素组成的数组
元组 Tuple: let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
x[5] = "world"; //OK, 字符串可以赋值给(string | number)类型
x[6] = true; // Error, 布尔不是(string | number)类型,联合类型
枚举: enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; //console.log(colorName)==>Green
Any: //不希望类型检查器对这些值进行检查,而是直接让它们通过编译阶段的检查
//它允许你在编译时可选择地包含或移除类型检查
let list: any[]=[1,true,"12"]
let notSure: any = 4;
notSure = "maybe";
notSure = false;
//Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法
let notSure: any = 4;
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
Void: //某种程度上来说,void类型像是与any类型相反,它表示没有任何类型.
//当一个函数没有返回值时,你通常会见到其返回值类型是 void
function warnUser(): void {
console.log("message");
}
Null 和 Undefined: //TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。
//和 void相似,它们的本身的类型用处不是很大
//注意:我们鼓励尽可能地使用--strictNullChecks
Never: //never类型表示的是那些永不存在的值的类型。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
object: //object表示非原始类型
//即除number,string,boolean,symbol,null或undefined之外的类型。
//使用object类型,就可以更好的表示像Object.create这样的API。例如:
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(123); // Error
类型断言: //有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型
let someValue:any="this is a string"
类型断言有两种形式。 其一是“尖括号”语法:
let strLength:number=(<string>someValue).length;
* 另一个为as语法:
let strLength: number = (someValue as string).length;
//两种形式是等价的。然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。
二. 变量声明
1.变量声明
let替换var
const 声明的变量,值不能改变
所有变量除了你计划去修改的都应该使用const
2. 解构
解构数组
1. let input = [1, 2];
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
2. let [first, ...rest] = [1, 2, 3, 4];
console.log(rest); // outputs [ 2, 3, 4 ]
对象解构
属性重命名:
let { a: newName1, b: newName2 } = o;
相当于:
let newName1 = o.a;
let newName2 = o.b;
令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
let {a, b}: {a: string, b: number} = o;
默认值:
默认值可以让你在属性为 undefined 时使用缺省值:
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
现在,即使 b 为 undefined , keepWholeObject 函数的变量 wholeObject 的属性 a 和 b 都会有值。
函数声明:
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
function f({ a, b = 0 } = { a: "" }): void {
// ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument
展开:
1. let defaults = { food: "spicy", price: "$$" };
let search = { ...defaults, food: "rich" };
===> search的值为{ food: "rich", price: "$$" }
注意:后面的属性覆盖前面相同名称的属性值
2. 当你展开一个对象实例时,会丢失其方法:
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
三. 接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。
TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
初识接口:
不使用接口来描述:
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
==>
使用接口来描述:必须包含一个label属性且类型为string:
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
注意:我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
可选属性:
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ? 符号.
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 比如,我们故意将createSquare里的color属性名拼错,就会得到一个错误提示
interface SquareConfig {
color?: string;
width?: number;
}
例子:
function createSquare(config:SquareConfig):{color:string,width:number}{
let newSquare={color:"white",area:100}
if (config.clor) {
// Error: Property 'clor' does not exist on type 'SquareConfig'
newSquare.color = config.clor;
}
}
let mySquare = createSquare({color: "black"});
只读属性:
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用readonly来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
let p1:Point={x:10,x1:20}
p1.x=4 //Error
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!
可用类型断言重写:
a = ro as number[];
readonly vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。
做为变量使用的话用 const,若做为属性则使用readonly
额外的属性检查:
报错的例子:
interface SquareConfig {
color?: string;
width?: number;
}
//类型化的是color,传入colour.
// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });
TypeScript认为以上这段代码可能存在bug。 对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候。如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
解决以上报错信息有几种方法:
- 最简便的方法是使用
类型断言
:
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
- 最佳的方式是能够添加一个
字符串索引签名
:
前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; //color和width以外的任意数量的属性
}
- 可能会让你感到惊讶的方式,将这个对象赋值给另一个变量: 因为squareOptions不会经过额外属性检查,所以编译器不会报错。
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
上面的方法只在squareOptions和SquareConfig之间有共同的属性时才好用。
在这个例子中,这个属性为width。如果变量间不存在共同的对象属性将会报错。例如:
let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);
在这里,如果支持传入color或colour属性到createSquare,你应该修改SquareConfig定义来体现出这一点
函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
可索引的类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap[“daniel”]。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
类类型
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员
类静态部分与实例部分的区别:
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
let digital = createClock(DigitalClock, 12, 17);
因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名
另一种简单方式是使用类表达式:
interface ClockConstructor {
new (hour: number, minute: number);
}
interface ClockInterface {
tick();
}
const Clock: ClockConstructor = class Clock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape{
color:string;
}
interface PenStroke{
penWidth:number;
}
interface Square extends Shape,PenStroke{
sideLength:numner;
}
let square = <Square>{};
square.color ='bule'
square.sideLength = 10
square.penWidth = 5
混合类型
因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。
一个对象可以同时做为函数和对象使用,并带有额外的属性:
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number): string { return '' };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
在使用JavaScript第三方库的时候,你可能需要像上面那样去完整地定义类型
接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 实现(略)
四、类
类
如果你使用过C#或Java,你会对这种语法非常熟悉。 我们声明一个Greeter类。这个类有3个成员:一个叫做greeting的属性,一个构造函数和一个greet方法
看一个使用类的例子:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
继承
类从基类中继承了属性和方法。 这里,Dog是一个派生类,它派生自Animal基类,通过extends关键字。 派生类通常被称作子类,基类通常被称作超类。
这一次,我们使用extends关键字创建了Animal的两个子类:Horse和Snake
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); } //必须先调用super()
move(distanceInMeters = 5) {
console.log("Slithering..."); //重写继承来的方法
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping..."); //重写继承来的方法
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
派生类包含了一个构造函数,它必须调用super(),它会执行基类的构造函数。 而且,在构造函数里访问this的属性之前,我们一定要调用super()。 这个是TypeScript强制执行的一条重要规则
公有,私有与受保护的修饰符
默认public
在TypeScript里,成员都默认为public,表示成员是可见的。
你也可以明确的将一个成员标记成public。 我们可以用下面的方式来重写上面的Animal类:
class Animal{
public name :string;
public constructor(theName:string){this.name=thename;}
public move(distanceInMeters:number){
console.log(`${this.name} moved ${distanceInMeters}m.`)
}
}
理解private
当成员被标记成private时,它就不能在声明它的类的外部访问.
class Animal{
private name:string;
constructor(theName:string){ this.name = theName;}
}
new Animal('cat').name; // 错误: 'name' 是私有的.
理解protected
protected修饰符与private修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以通过实例方法访问.
readonly修饰符
你可以使用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.." //错误,name是只读的属性
参数属性
存取器:
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
把一个简单的类改写成使用get和set:
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
alert(employee.fullName);
}
对于存取器有下面几点需要注意的:
首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有get不带有set的存取器自动被推断为readonly。 这在从代码生成.d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
静态属性
抽象类
高级技巧
构造函数
把类当做接口使用
函数
为函数定义类型:
let myAdd = function(x: number, y: number): number { return x + y; }
书写完整函数类型:
函数的类型只是由参数类型和返回值组成的
let myAdd: (x:number, y:number) => number =
function(x: number, y: number): number { return x + y; };
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型,因此我们通常省略它。返回值类型是函数类型的必要部分,如果函数没有返回任何值,你也必须指定返回值类型为void而不能留空.
推断类型
可选参数和默认参数:
传递给一个函数的参数个数必须与函数期望的参数个数一致
参数加?
表示可选参数时,其必须跟在必须参数后面
JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。
在TypeScript里我们可以在参数名旁使用?实现可选参数的功能:
function buildName(firstName : string, lastName? : string){
if(lastName)
return "1"
else
return "2"
}
let result1 = buildName("bod"); //correctly
let result1 = buildName("bod",'adam','srt'); //error
在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时。 它们叫做有默认初始化值的参数.
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); //correctly
剩余参数:
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用arguments来访问所有传入的参数。
在TypeScript里,你可以把所有参数收集到一个变量里
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个
this:
重载:
泛型
我们需要一种方法使返回值的类型与传入参数的类型是相同的.
基本类型:
function identity(arg: number): number {
return arg;
}
==> 泛型:传入数值类型并返回数值类型
function identity<T>(arg: T): T {
return arg;
}
定义了泛型函数后,可以用两种方法使用:
第一种:
let output = identity<string>("myString"); // type of output will be 'string'
第二种:
利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let output = identity("myString"); // type of output will be 'string'
如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的
我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
使用泛型变量:
现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。
我们可以像创建其它数组一样创建这个数组:
写法一:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
写法二:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时T的的类型为number。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。