交叉类型

交叉类型是将多个类型合并为一个类型,这让我们可以把现有的多种类型叠加在一起成为一种类型
,它包含了所需的所有类型的特性。

我们大多是在混入(mixins)或其他不适合典型面向对象模型的地方看到交叉类型的使用

function extend<T,U>(first:T,second:U):T&U {
    let result = <T&U>{};
    for(let id in first){
        (<any>result)[id]=(<any>first)[id];
    }
    for(let id in second){
        if(!result.hasOwnProperty(id)){
            (<any>result)[id]=(<any>second)[id];
        }
    }
    return result;
}

class Person{
    constructor(public name:string) {
    }
}
interface Loggable{
    log():void;
}
class ConsoleLogger implements Loggable{
    log() {
    }
}
var jim = extend(new Person('jim'),new ConsoleLogger());
var n = jim.name;
jim.log();

联合类型

Takes a string and adds “padding” to the left.

  • If ‘padding’ is a string, then ‘padding’ is appended to the left side.
  • If ‘padding’ is a number, then that number of spaces is added to the left side.

//此处存在一个问题,padding 参数的类型指定成了any,

function padLeft(value:string,padding:any) {
    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}".`);
}
padLeft('heelo world',4)

' hello world'以传入一个既不是number,也不是string类型的参数,但是TypeScript不报错代替any,我们可以使用联合类型做为padding的参数

function padLeft(value:string,padding:string|number) {

}
let indentedString = padLeft("Hello world", true);

此处在编译阶段就会报错

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员

interface Bird{
    fly();
    layEggs();
}
interface Fish{
    swim();
    layEggs();
}
function getSmallPet(): Fish | Bird {
    //...
}
let pet = getSmallPet();
pet.layEggs();
pet.swim()//报错没有此属性

类型保护与区分类型

js里常用来区分两个可能值的方法是检查成员是否存在,如之前提及的,我们只能访问联合类型中共同拥有的成员。

let pet = getSmallPet();
if((<Fish>pet).swim()){
    (<Fish>pet).swim();
}
else{
    (<Bird>pet).fly();
}

在此类型断言就不会报错

用户自定义的类型保护

Typescript的类型保护机制让一次类型检查,之后都清楚变量的类型成为现实。

类型保护就是一些表达式,他们会在运行时检查以确保在某个作用域里的类型,要定义一个类型保护

我们只要简单地定义一个函数,它的返回值是一个类型谓词

function isFish(pet:Fish|Bird):pet is Fish {
    return (<Fish>pet).swim!==undefined;
}

在此处示例中,pet is Fish就是类型谓词,谓词为parameterName is TYPE 这种形式

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

当调用isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量原始类型是兼容的

typeof类型保护

function isNumber(x: any): x is number {
    return typeof x === "number";
}

function isString(x: any): x is string {
    return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding)) {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

必须定义一个函数来判断原始类型,过于繁琐。我们可以使用typeof x==='number'抽象成一个函数

function padLeft(value: string, padding: string | number) {
    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}'.`);
}

typeof 类型保护只有两种形式能被识别:typeof v = "typename"typeof v !=="typename",此处出typename必须是number,string,boolean或者symbol

instanceof类型保护

instanceof类型保护是通过构造函数来细化类型的一种方式

interface  Padder{
    getPaddingString():string;
}
class SpaceRepeatingPadder implements Padder{
    constructor(private numSpaces:number) {
    }
    getPaddingString(): string {
        return Array(this.numSpaces+1).join(" ");
    }
}
class StringPadder implements Padder{
    constructor(private value:string) {
    }
    getPaddingString(): string {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random()<0.5?
        new SpaceRepeatingPadder(4):
        new StringPadder("  ");
}
//类型为SpaceRepeatingPadder|StringPadder
let padder:Padder=getRandomPadder();
if(padder instanceof SpaceRepeatingPadder){
    padder;//类型细化为SpaceRepeatingPadder
}
if(padder instanceof StringPadder){
    padder;//类型细化为StringPadder;
}

instanceof的右侧要求是一个构造函数
1、此构造函数的prtotype属性的类型,如果它的类型不为any的话

2、构造签名所返回的类型的联合

可以为null的类型

--strictNullChecks标记可以解决此错误:当你声明一个变量时,它不会自动地包含null
undefined。你可以使用联合类型明确的包含他们

let s="foo";
s=null;//错误,‘null’不能赋值给'string'
let sn:string|null = "bar";
sn = null//可以
sn = undefined;//error,'undefined‘不能赋值给string|null

可选参数和可选属性

使用了--strictNullChecks,可选参数会被自动地加上|undefined:

function f(x:number,y?:number) {
    return x+(y||0);
}
f(1,2);
f(1);
f(1,undefined);
f(1,null);//error,'null' is not assignable to "number|undefined"

可选属性有同样的处理

class C{
    a:number;
    b?:number;
}
let c = new C();
c.a=12;
c.a=undefined;//error,
c.b=13;
c.b=undefined;//OK
c.b=null;//error

类型保护和断言

由于可以为null 的类型是通过联合类型实现,那么你需要使用类型保护来去除null

function f(sn:string | null ):string {
    return sn || "default";
}

如果编译器不能够去除null或undefined吗,你可以使用类型断言手动去除。语法是添加!后缀

identifier!identifier的类型里去除了nullundefined:

function broken(name:string|null):string {
    function postfix(epithet:string) {
        return name.charAt(0)+". the" + epithet;
        //error,name is possibly null
    }
    name =name ||"Bob";
    return postfix("great");
}

function fixed(name:string|null):string {
    function postfix(epithet:string) {
        return name!.charAt(0)+". the" + epithet;//OK
    }
    name =name ||"Bob";
    return postfix("great");
}

本例使用了嵌套函数,因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式
)。 因为它无法跟踪所有对嵌套函数的调用,尤其是你将内层函数做为外层函数的返回值。
如果无法知道函数在哪里被调用,就无法知道调用时 name的类型

//类型别名
//类型别名会给一个类型起一个新名字。类型别名有时和接口很像,但是可以作用于原始值
//联合类型,元组以及其他任何你需要手写的类型。
// type Name = string;
// type NameResolver = ()=> string;
// type NameorResolver = Name | NameResolver ;
// function getName(n:NameorResolver):Name {
//     if(typeof n === 'string'){
//         return n;
//     }else{
//         return n();
//     }
// }
//起别名不会新建一个类型,它创建了一个新名字来引用那个类型。给原始类型起别名通常没什么用。

//接口与类型别名的区别
/*一、接口创建了一个新的名字,可以在其他任何地方使用。
* 类型别名并不创建新名字,比如,错误信息就不会使用别名。
* 在编译器中将鼠标悬停在interfaced上,显示返回的是interface,悬停在aliased上时
* 显示是对象字面量类型*/
// type Alias = {num :number}
// interface Interface{
//     num:number;
// }
// declare function aliased(arg:Alias):Alias;
// declare function interfaced(arg:Interface):Interface;
//另一个重要区别是类型别名不能被extends和implements,
//因为软件中的对象应该是对于扩展石开放的,但是对于修改是封闭的,你应该尽量去使用接口替代类型别名

//字符串字面量类型
//字符串字面量类型允许你指定字符串必须的固定值,在实际应用中,字符串字面量类型可以
//于联合类型,类型保护和类型别名很好的配合。可以实现类似枚举类型的字符串
// type Easing = "ease-in" | "ease-out" | "ease-in-out";
// class UIElement {
//     animate(dx:number,dy:number,easing:Easing){
//
//     }
// }
// let button = new UIElement();
// button.animate(0,0,"ease-in");
//此处只能从三种允许的字符中选择其一来作为参数传递。

//可辨识联合
//你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式
/*具有普通的单例类型属性——可辨识的特征
* 一个类型别名包含了那些类型的联合——联合
* 此属性上的类型保护
* */
// interface Square {
//     kind:"square";
//     size:number;
// }
// interface Rectangle {
//     kind:"rectangle";
//     width:number;
//     height:number;
// }
// interface Circle {
//     kind:"circle";
//     radius:number;
// }
//首先我们声明了将要联合的接口,每个接口都有kind属性但有不同的字符串字面量类型。
//kind属性称作可辨识的特征或标签。其他的属性则特定于各个接口。注意,目前接口间没有联系。
// type Shape = Square | Rectangle | Circle;
// function area(s:Shape) {
//     switch (s.kind){
//         case "square": return;
//         case "circle": return;
//         case "rectangle":return;
//     }
// }

//多态的this类型
// class BasicCalculator{
//     public constructor(protected value:number=0){}
//     public currentValue():number{
//         return this.value;
//     }
//     public add(operand:number): this{
//         this.value += operand;
//         return this;
//     }
//     public multiply(operand:number):this{
//         this.value *=operand;
//         return this;
//     }
// }
// let v = new BasicCalculator(2)
//             .multiply(5)
//             .add(1)
//             .currentValue();

//由于这个类型使用了this类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变
// class ScientificCalculator extends BasicCalculator{
//     public constructor(value = 0) {
//         super(value);
//     }
//     public sin(){
//         this.value = Math.sin(this.value);
//         return this;
//     }
// }
// let m = new ScientificCalculator(2)
//     .multiply(5)
//     .sin()
//     .add(1)
//     .currentValue();

//索引类型
/*索引类型,编译器就能够检查使用动态属性名的代码。例如,一个常见的js
* 模式是从对象中选取属性的子集*/
// function pluck(o,names) {
//     return names.map(n => o[n]);
// }
//在ts中使用此函数,通过索引类型查询和索引访问操作符
function pluck<T,K extends keyof T>(o:T,names:K[]):T[K][] {
    return names.map(n => o[n]);
}
interface Person{
    name:string;
    age:number;
}
let person:Person={
    name:'Jarid',
    age:35
};
// let strings:string[]=pluck(person,['name']);//ok,string[]

//编译器会检查name是否真的是Person的一个属性。本例还引入了几个新的类型操作符
//首先是keyof T,索引类型查询操作符。对于任何类型T,keyof T的结果为T上已知的公共属性名的联合
let personProps:keyof Person;//"names"|"age"