TypeScript

163 阅读19分钟

1.typescript是什么

  • Typescript是由微软开发的一款开源的编程语言
  • Typescript是Javascript的超集,遵循最新的ES5/ES6规范。TypeScript扩展了Javascript语法
  • TypeScript更像后端Java、C#这样的面向对象语言可以让JS开发大型企业应用
  • 越来越多的项目是基于TS的,比如VSCode、Angular6、Vue3、React16,。TS提供的类型系统可以帮助我们在写代码的时候提供更丰富的语法提示。
  • 在创建前的编译阶段经过类型系统的检查,就可以避免很多线上的错误

image.png

2.typescript的安装和编译

2.1 安装

npm i typescript -g

2.2:生成配置文件

tsc -- init
tsc helloWorld.ts

配置文件tsconfig.json.主要是配置typeScript 如何进行向js的转换

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}
"target": "ES5", // 编译的目标
"module": "commonjs",//编译的模块化规范
let arr1 :number[] = [1,2,3];// 两行代码是等价的
let arr2 : Array<number=[4,5,6];

元组类型(tuple):已知数量和类型的数组

let a:[string,number]=['ddd',10];

每个数组元素的类型都是确定的,且数组的元素数量是确定的,不能多写、也不能少写,否则编译期报错

枚举类型(enunm)

普通枚举

enum Gender{
    GIRL,
    BOY
}

console.log(Gender.BOY)
console.log(Gender.GIRL)

编译出来的:

var Gender;
(function (Gender) {
    Gender[Gender["GIRL"] = 0] = "GIRL";
    Gender[Gender["BOY"] = 1] = "BOY";
})(Gender || (Gender = {}));
console.log(Gender.BOY);
console.log(Gender.GIRL);

可以看到,枚举就是一个自执行函数 通过Gender["GIRL"] = 0将 Gender的GIRL字段赋为 0 , Gender[Gender["GIRL"] = 0] = "GIRL"又将Gender的0字段赋为 'GIRL'

console.log(Gender.BOY, Gender[1]);// 0,BOY
console.log(Gender.GIRL, Gender[0]);// 1 GRIL

常量枚举

const enum Colors{
    RED,YELLOW,BLUE
}

let myColor=[Colors.RED,Colors.BLUE,Colors.YELLOW]

编译结果

var myColor = [0 /* Colors.RED */, 2 /* Colors.BLUE */, 1 /* Colors.YELLOW */];

可以减少代码的输出,直接编译为相应的值

any :

any类型的变量可以被赋值任意类型的值。如果一个变量被赋予的any 类型,那么他和js就一样,不进行类型检查

let root:any =document.getElementById('root')
root.style.color='red'


let elment:HTMLElement | null =document.getElementById('root')
elment.style.color='red'

编译结果

var root = document.getElementById('root');
root.style.color = 'red';
var elment = document.getElementById('root');
elment.style.color = 'red';

null undefined 是其它类型的子类型,可以赋值给其他的类型 以下可以编译

let x:number;
x=1;
x=undefined;
x=null; 

never 代表不会出现的值 never几乎不用

//1.作为不会返回的函数的返回值 类型
function error(message:string):never{
   throw new Error('报错了');//直接异常结束 了,没有返回值
   console.log('ok');
}

死循环,不会结束,没有返回值

function loop():never{
 while(true){

 }
  console.log('ok');
}
function fn(x:number|string){
    if(typeof x === 'number'){
        console.log(x);
    } else if (typeof x === 'string'){
        console.log(x);
    }else{
        console.log(x);//never
    }
}

void:代表没有任何类型

函数没有返回值,那么就是void类型 如果返回值为 undefined 那么能通过,因为 undefined 可以赋给 void

function greeting():void{
    //return null;
}

如果strictNullChecks=false, null可以赋值给void,

如果strictNullChecks=true,null不可以赋值给void

void never的区别:

void可又被赋值为null(看配置的情况)、undefined,never不能包含任何类型

返回类型为void的函数能够正常执行,但是返回never的函数无法正常执行(异常或者死循环)

Symbol

const s1 = Symbol('key');
const s2 = Symbol('key');
console.log(s1 == s2); // false  会提示TS2367: This condition will always return 'false' since the types 'typeof s1' and 'typeof s2' have no overlap.

BigInt

const max = Number.MAX_SAFE_INTEGER;//2**53-1
console.log(max+1 ===max+2);// true

最大值再加没有意义,溢出了,+1、+2一样的


const max = BigInt(Number.MAX_SAFE_INTEGER);
console.log(max + BigInt(1) === max +BigInt(2)); // false
//console.log(max + 1n === max + 2n); //false
//数字后面加n 代表大整型
let foo:number;
let bar:bigint;
foo = bar; // 报错
bar =foo;// 报错

两者之间不能相互赋值

注意JS里的类型 Number BigInt
ts里的类型 number bigint

类型推断

如果一个变量声明时没有类型且没有赋值,那么他就可以视为any类型

let a;
a=1;
a='ddddd'

以上都能通过

但是,如果该变量虽然没有显性的确定类型,但是在声明的时候进行了赋值,该变量就是声明时的类型

let a= 1;
a='sss' //报错

联合类型:取值可以为多种类型的某一种

let name3 : string | number;
// 赋值前只能访问共有方法
console.log(name3!.toString());


name3 = 3;
// 赋值后可以访问某种类型的方法
console.log(name3.toFixed(2));

name3='zhufeng';
// 赋值后可以访问某种类型的方法
console.log(name3!.length);

交叉类型

多种类型的集合,联合对象将具有所联合类型的所有成员

interface People {
  age: number,
  height: number
}
interface Man{
  sex: string
}
const xiaoman = (man: People & Man) => {
  console.log(man.age)
  console.log(man.height)
  console.log(man.sex)
}
xiaoman({age: 18,height: 180,sex: 'male'});

类型断言:将一个联合类型的 变量 指定为更加具体的量

let name4:string|number;
console.log((name4! as number).toFixed(2));
console.log((name4! as string).length);
// console.log((name4! as boolean).length); //会报错 联合类型中没有boolean

双重断言:先断言为any 再断言为 boolean

console.log(name4! as any as boolean);//正常编译

字面量类型和类型字面量

  • 字面量类型
const up:'Up'= 'Up';
const down: 'Down' = 'Down';
const left: 'Left' = 'Left';
const right: 'Right' = 'Right';
type Direction ='Up'|'Down'|'Left'|'Right';
//可实现枚举的效果
function move(direction: Direction){

}
move("Down");
  • 类型字面量
type Person = {
    name:string,
    age:number
}
let p1: Person={
    name:'zhufeng',
    age:10
}
  • 字符串字面量和联合类型
//
type T1 = '1'|'2'|'3';
type T2 = string|number|boolean;


let t1:T1 = '1';
let t3:T1='45'// 报错 T1类型的值只能是'1','2','3'


let t2:T2 = true;

T1类型的值只能是'1','2','3'

函数

函数定义写法

function hello(name:string):void{
  console.log('hello',name);
}
hello('zhufeng');

函数可以约束参数和返回值

函数表达式写法

type GetName = (firstName:string,lastName:string)=>string;
let getName: GetName = function (firstName: string, lastName: string): string{
    return firstName+lastName;
}

可选参数 : 使用?来表示某个参数可传可不传

function print(name:string,age?:number):void{
  console.log(name,age);
  
}
print('zhufeng',11);
print('zhufeng');

默认参数 :不传 就使用默认值

function ajax(url:string,method:string='GET'){
  console.log(url,method);
}
ajax('/');

剩余参数

function sum(...numbers:number[]){
    return numbers.reduce((val,item)=>val+item,0)
}
console.log(sum(1,2,3));

函数的重载:

let obj:any = {};
//function attr():void
/**
 * 如果传的val是一个字符串赋给obj.name
 *                 数字,赋给obj.age
 * @param val
 */
function attr(val: string): void
function attr(val: number): void
function attr(val:any):void{
    if(typeof val === 'string'){
        obj.name = val;
    } else if (typeof val === 'number'){
        obj.age = val;
    }
}
attr('zhufeng');
attr(10);
//attr(true);
function add(a: string,b:string): void
function add(a: number,b:number): void
function add(a: string|number,b:string|number): void {
}
add('a','b');
add(1,2);
//add(1,'b');

这种写法的函数声明和函数实现必须紧紧贴在一起 多个声明,一个实现

如何定义类

ts 文件中 出现 export 或者 import ts 会认为该文件是一个模块,其内部的变量变为模块私有 否则多个不是模块的ts文件中定义的变量会都挂在全局,会出现命名冲突

//exports.__esModule = true;
export {} // 出现 export 或者 import ts 会认为该文件是一个模块,其内部的变量变为模块私有


class Person{
    name:string='zhufeng';
    getName():void{
        console.log(this.name);
        
    }
}
let p1 = new Person();
p1.name = 'zhufeng';
p1.getName();

编译结果

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Person = /** @class */ (function () {
    function Person() {
        this.name = 'zhufeng';
    }
    Person.prototype.getName = function () {
        console.log(this.name);
    };
    return Person;
}());
var p1 = new Person();
p1.name = 'zhufeng';
p1.getName();

__esModule 表示是es6模块

定义存取器

class User{
    constructor(public myName:string){
        this.myName = myName
    }
    get name(){
        return this.myName;
    }
    set name(value){
        this.myName = value;
    }
}
let user = new User('zhufeng');
user.name = 'jiagou';
console.log(user.name);

编译结果

var User = /** @class */ (function () {
    // myName:string;
    function User(myName) {
        this.myName = myName;
        this.myName = myName;
    }
    Object.defineProperty(User.prototype, "name", {
        get: function () {
            return this.myName;
        },
        set: function (value) {
            this.myName = value;
        },
        enumerable: false,
        configurable: true
    });
    return User;
}());
var user = new User('zhufeng');
user.name = 'jiagou';
console.log(user.name);

readonly的使用:只读属性不能被赋值

class Animal{
    public readonly name:string;
    constructor(name:string){
        this.name = name;
    }
    changeName(name: string){
        this.name = name; // 报错 只读属性不能被赋值
    }
}

类的继承

子类拥有父类身上的所有方法和属性

export {}
class Person{
    name:string;
    age:number;
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
    getName():string{
        return this.name;
    }
    setName(name:string):void{
        this.name = name;
    }
}
class Student extends Person{
    stuNo:number;
    constructor(name: string, age: number, stuNo: number){
        super(name,age);
        this.stuNo = stuNo;
    }
    getStuNo(){
        return this.stuNo;
    }
}

let s1= new Student('zhufeng',11,1);

类的修饰符:public、private、protected

//public private protected

class Father {
    static fatherName: string = 'fatherName';
    toString(){console.log('Father');
    }
    public name: string;//public 自己 自己的子类 和其它类都能访问
    protected age: number;//protected 自己和自己子类能访问,其它类不能访问
    private money: number;//private 自己能访问,子类和其它类不能访问
    constructor(name: string, age: number, money:number) {
        this.name = name;
        this.age = age;
        this.money = money;
    }
    getName(): string {
        return this.name;
    }
}
class Child extends Father {
    static childName: string = 'childName';
    constructor(name: string, age: number, money: number) {
        super(name, age,money);
    }
    public toString() {
        super.toString();
        console.log('Child');
    }
    public desc(){
        console.log(this.name,this.age);
    }
}
//动物 哺乳动物
let father = new Father('zhufeng', 11, 1);
//father.toString()
let child = new Child('zhufeng',11,1);
//console.log(child.name);

child.toString() // Father Child

Child.fatherName; // 可以访问
Child.childName;// 可以访问// 可以访问
child.age;// 可以访问
child.money; //报错,父类私有属性,子类实例不能访问

这里其实还存在子类重写父类的方法的例子,此处不讨论 、

类的装饰器:不聊了,以后再说

interface:接口

  • 接口一方面可以在面向对象编程中表示为 行为的抽象,另外可以用来描述对象的形状
  • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
  • 一个类可以继承另一个类并实现多个接口
  • 接口像插件─样是用来增强类的,而抽象类是具体类的抽象概念
  • 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类的可以有多个子类,但只能有一个父类

接口

  • interface中可以用分号或者逗号分割每一项,也可以什么都不加
  • 同名的接口可以写多少,类型会自动合并

描述对象的形状

//描述对象的形状
interface Speakable{
    name:string;
    speak():void
}

let speakMan: Speakable={
    name:'zhufeng',
    speak(){}
}

行为的抽象

interface Speakable{
    speak():void
}
interface Eatable {
    eat(): void
}

一个类可以继承另一个类并实现多个接口

class Person implements Speakable, Eatable{
    name: string
    speak() {
        throw new Error("Method not implemented.")
    }
    eat(): void {
        throw new Error("Method not implemented.")
    }
}

接口的继承

interface Speakable2{
    speak():void
}
interface SpeakChinese extends Speakable2{
    speakChinese(): void
}
class ChineseMan implements SpeakChinese{
    // 下面的代码可以类似java的 getter、setter 自动生成
    speakChinese(): void {
        throw new Error("Method not implemented.")
    }
    speak() {
        throw new Error("Method not implemented.")
    }
}

函数类型接口:修饰方法(函数)

interface Discount{
   (price:number):number
}
const discount: Discount = (price: number): number=>{
    return price*.8;
}

可索引接口:对数组和对象进行约束

interface User{
    [xx:number]:string
}
let user: User={
    0:'0',1:'1',2:'2'
}
let arr: User=['1','2','3'];

构造函数类型的接口修饰

class Animal{
    constructor(public name:string){

    }
}

constructor(public name:string) public name:string 会给类增加一个name属性

//如果是修饰普通函数 
//加上new之后就是用来描述类的构造函数
interface WithNameClass{
    //删掉new 就会报错
    new(name:string):any
}
let wc: WithNameClass = Animal
function createClass(clazz: WithNameClass,name:string){
    return new clazz(name);
}
let a = createClass(Animal,'zhufeng');
console.log(a.name);

new(name:string):any //加上new之后就是用来描述类的构造函数

泛型:

泛型工厂函数

//创建一个长度为length的数组,里面的值用value填充
 function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}
let result = createArray<string>(3,'x');
console.log(result);

T就是泛型 代表之后传入的类型

泛型类

class MyArray<T>{
   private list:T[]=[]
   add(value:T){
     this.list.push(value);
   }
   getMax():T{
     return this.list[0];
   }

}
let array = new MyArray<number>();
array.add(1);
array.add(2);
array.add(3);
console.log(array.getMax());

new 函数 泛型与类的构造函数

 function factory<T>(type:{new():T}):T{
  return new type();
 }
 class Person{}
 let p = factory<Person>(Person);
console.log(p);

泛型接口

在接口上写泛型

interface Calculate<T>{
  (a:T,b:T):T
}

let sum: Calculate<number> = function (a: number, b: number): number {
  return a + b;
};
sum(1,2);

let sum: Calculate<number>、sum(1,2);

第二种写法

在方法上写泛型

interface Calculate2{
  <T>(a:T,b:T):T
}
let sum2: Calculate2 = function <T>(a: T, b: T): T {
  return a;
};
sum2<number>(1, 2);

let sum2: Calculate2sum2<number>(1, 2);

泛型可以写多个

function swap<A,B>(tuple:[A,B]):[B,A]{
   return [tuple[1],tuple[0]];
}

默认泛型:有点默认参数的意思了

 function createArray<T=number>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
} 
interface T2<T=string>{

}

type T22 = T2; // 如果不写T=string的话,这里就会要求T必须传一个类型参数

泛型继承(重要

下面的函数是会报错的,因为T可以是任何类型,所以他不一定有length属性

 function logger<T>(val:T){
  console.log(val.length);
} 

这时我们定义一个有length的接口

interface LengthWise{
  length:number
}

T继承接口

//非常非常非常重要
function logger2<T extends LengthWise>(val: T) {
  console.log(val.length);
}
let obj  = {
  length:10
}
type Obj = typeof obj;
logger2<Obj>(obj);

检查的时候会进行鸭子检查duck-check);即要求传入的泛型类型必须要有 继承的接口的属性

duck-check:判断兼容不兼容跟extends继承没有一点关系 ,只看形状 有没有对应的属性

泛型类型别名

类型别名可以重命名一些复杂的类型

type Cart<T> = {list:T[]}|T[];

表示 一个变量要么是包含list属性的 T数组、要不然就是T数组

let c1:Cart<string> = {list:['1']};
let c2: Cart<number> = [1,2,3]; 

示例:redux的compose函数

泛型接口vs泛型类型别名

  • 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字,例如报错信息就不会使用别名

  • 类型别名不能被extends和 implements,这时我们应该尽量使用接口代替类型别名

  • 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适

  • 能用interface实现的不要用type

    一般是联合类型 或者元组类型采用 type

  • 两者都可以扩展,但是语法又有所不同

Interface extends interface

interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }

Type alias extends type alias

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

Interface extends type alias

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

Type alias extends interface

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

类型的兼容性

  • 如果传入的变量和声明的类型不匹配,TS就会进行兼容性检查
  • 原理是Duck-Check ,就是说只要**目标类型中声明的属性变量在源类型中都存在就是兼容的**

目标类型:函数声明的参数类型

源类型:函数使用时实际传入的参数类型

所以一般实际传入的参数会是声明的参数的子类型、因为父类型有的参数子类型一定有(当然执行duck-check的ts 完全不在乎有没有继承关系,只要确定目标类型中声明的属性变量在源类型中都存在就是兼容的)

1.接口的兼容性

interface Animal{
    name:string;
    age:number
}
interface Person{
    name:string;
    age:number;
    gender:number
}
function getName(a:Animal):string{
  return a.name;
}
let a: Animal = {
    name:'',
    age:10
}
getName(a);
let p: Person = {
  name: "",
  age: 10,
  gender:0
};
getName(p);

可以看到、 getName(a:Animal)要求是个Animal类型的参数,可是因为Person类型的参数拥有Animal的所有属性,所以类型检查也能通过

2. 基本数据类型的兼容性:

 let num:string |number;
let str:string = 'zhufeng';
num = str;

let num2:{
    toString():string
}
let str2:string = 'jiagou';
num2 = str2;

没什么好说的

类的兼容性

namespace ab{
    class Animal {name:string}
    class Bird extends Animal {age:number}
    let a: Animal;
    let b: Bird;
    a=b;
    // b=a;
}

因为 b是Bird类型,拥有所有Animal类型的变量,所以a=b可以赋值

但是,反过来b=a就不行,因为a没有拥有b的Bird的所有属性 如果a能够赋给b,那么b调用Brid类型独有的属性时就会出现属性不存在的尴尬

函数的兼容性(难点)----参数逆变父类 返回值协变子类

函数的兼容性 需要比较参数和返回值

示例如下

type Func = (a:number,b:number)=>void;

定义一个函数类型,参数是(a:number,b:number),返回值是 void

interface Func2{
    (a:number,b:number):void
}// 与上面的效果一样,interface 和 type 很多时候拥有相同功能

声明一个函数

let sum: Func;
function f1(a: number, b: number):void {
}

两者参数类型、返回值类型完全一样,当然可以sum = f1;

sum = f1;

声明函数f2 他比Func 类型的参数少一个

function f2(a: number): void {}

sum = f1;也能通过编译

//少二个参数也可以
function f3(): void {}
sum = f3;
function f4(a: number, b: number,c:number): void {}
sum = f4;// 报错

对于函数的兼容在原理上我们可以这么理解

class Animal {}
class Dog extends Animal {
  public name: string = "Dog";
}
class BlackDog extends Dog {
  public age: number = 10;
}
class WhiteDog extends Dog {
  public home: string = "北京";
}
let animal: Animal;
let dog: Dog;
let blackDog: BlackDog;
let whiteDog: WhiteDog;
type Callback = (dog: Dog) => Dog;
function exec(callback: Callback): void {}
/**
 * 参数可以传自己和自己的父类
 * 返回值可以传自己和自己的子类
 * 四种情况
 * 1.参数传子类返回值子类  y
 * 2.参数是子类返回值是父类 n
 * 3.参数是父类返回值是父类 y
 * 4.参数是父类返值是子类 y
 */
type ChildToChild = (blackDog: BlackDog) => BlackDog;
let childToChild: ChildToChild;
//exec(childToChild);//n
type ChildToParent = (blackDog: BlackDog) => Animal;
let childToParent: ChildToParent;
//exec(childToParent);//n
type ParentToParent = (animal: Animal) => Animal;
let parentToParent: ParentToParent;
//exec(parentToParent);//n
type ParentToChild = (animal: Animal) => BlackDog;
let parentToChild: ParentToChild;
exec(parentToChild);//y

[以下参考别人的](协变与逆变 | 深入理解 TypeScript (jkchao.github.io))

子类型 在编程理论上是一个复杂的话题,而他的复杂之处来自于一对经常会被混淆的现象,我们称之为协变逆变。这篇文章将会解释上述两个概念。

开始文章之前我们先约定如下的标记:

  • A ≼ B 意味着 A 是 B 的子类型。
  • A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。
  • x : A 意味着 x 的类型为 A

#一个有趣的问题

假设我有如下三种类型:

Greyhound ≼ Dog ≼ Animal

Greyhound (灰狗)是 Dog (狗)的子类型,而 Dog 则是 Animal (动物)的子类型。由于子类型通常是可传递的,因此我们也称 Greyhound 是 Animal 的子类型。

问题:以下哪种类型是 Dog → Dog 的子类型呢?

  1. Greyhound → Greyhound
  2. Greyhound → Animal
  3. Animal → Animal
  4. Animal → Greyhound

让我们来思考一下如何解答这个问题。首先我们假设 f 是一个以 Dog → Dog 为参数的函数。它的返回值并不重要,为了具体描述问题,我们假设函数结构体是这样的: f : (Dog → Dog) → String

现在我想给函数 f 传入某个函数 g 来调用。我们来瞧瞧当 g 为以上四种类型时,会发生什么情况。

1. 我们假设 g : Greyhound → Greyhound, f(g) 的类型是否安全?

不安全,因为在f内调用它的参数(g)函数时,使用的参数可能是一个不同于灰狗但又是狗的子类型,例如 GermanShepherd (牧羊犬)。但是,g函数内部可能会使用Greyhound 的独有的属性和方法,GermanShepherd (牧羊犬)不存在相应的属性和方法,会报错

2. 我们假设 g : Greyhound → Animal, f(g) 的类型是否安全?

不安全。理由同(1)。

3. 我们假设 g : Animal → Animal, f(g) 的类型是否安全?

不安全。因为 f 有可能在调用完参数函数g之后,让返回值,也就是 Animal (动物)狗叫(函数f 声明的参数函数的 返回值是 Dog类型,所以当然有可能让返回值进行狗叫)。并非所有动物都会狗叫。 则会出现返回值中没有参数函数声明的返回值的属性的问题,不安全

4. 我们假设 g : Animal → Greyhound, f(g) 的类型是否安全?

是的,它的类型是安全的。首先,f 可能会以任何狗的品种来作为参数调用 g,而所有的狗都是动物。其次,它可能会假设结果是一条狗(声明中f : (Dog → Dog) → String,g调用的结果当然会被当作是Dog),而真实函数返回的所有的灰狗都是狗,拥有狗的所有属性和方法。

#展开讲讲?

如上所述,我们得出结论:

(Animal → Greyhound) ≼ (Dog → Dog)

返回值类型很容易理解:灰狗是狗的子类型。但参数类型则是相反的:动物是狗的父类

用合适的术语来描述这个奇怪的表现,可以说我们允许一个函数类型中,返回值类型是协变的,而参数类型是逆变的。返回值类型是协变的,意思是 A ≼ B 就意味着 (T → A) ≼ (T → B) 。参数类型是逆变的,意思是 A ≼ B 就意味着 (B → T) ≼ (A → T) ( A 和 B 的位置颠倒过来了)。

一个有趣的现象:在 TypeScript 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes 或 --strict 标记来修复这个问题。