TypeScript 的理解

265 阅读10分钟

tsconfig.json 文件配置项的信息

{
  "compilerOptions": {
    /* Basic Options */ 
    // "incremental": true,                   //增量编译
    // "tsBuildInfoFile": "./",               //增量编译文件的存储位置
    // "diagnostics",                          //打印诊断信息

    //"target": "es5",                          //编译后的语言版本
    //"module": "commonjs",                      //生成代码的模块标准
    // "outFile": "./",                        //将多个相互依赖的文件生成一个文件, 可以用在AMD模块中
    // "lib": [],                             //ts需要引用的库,即声明文件,es5默认dom, "Es5""scripthost"
    
    // "allowJs": true,                       //允许编译JS文件(js、jsx)
    // "checkJs": true,                       //允许在JS文件中报错, 通常跟allowJs一起使
    // "outDir": "./",                        //指定输出目录
    // "rootDir": "./",                       //指定文件输出目录(用于输出)
    //composite: true                         //工程可以被引用并且进行增量编译
    //references: []                          //依赖的功程

    // "declaration": true,                   //生成声明文件
    // "declarationDir": "./d",               //声明文件的路径
    // "emitDeclarationOnly": true            //只生成声明文件
    // "declarationMap": true,                //生成声明文件的sourceMap
    // "sourceMap": true,                     //生成目标文件的sourceMap
    // "inlineSourceMap": true                //生成目标文件的 inline sourceMap
    // "typeRoots": [],                       //声明文件目录,默认 node_modules/@types
    // "types": [],                           //声明文件包
    
    // "removeComments": true,                //删除注释
    
    // "noEmit": true,                        //不输出文件
    // "noEmitOnError": true                  //发生错误时不输出文件
    
    // "importHelpers": true,                 //通过tslib引入helper函数, 文件必须时模块
    // "noEmitHelpers": true,                 //不生成helper函数,需额外安装 ts-helpers
   
    // "downlevelIteration": true,            //降级遍历器的实现 (es3/5)

    /* Strict Type-Checking Options */
   // "strict": true,                           //开启所有的严格类型检查
   // "alwaysStrict": true,                  //在代码中注入 "use strict"
    // "noImplicitAny": true,                 //不允许隐式的 any 类型
    // "strictNullChecks": false,              //不允许把null、undefined 赋值给其他类型变量
    // "strictFunctionTypes": false,           //不允许函数参数的双向协变
    // "strictPropertyInitialization": true,   //类的实例属性必须初始化
    // "strictBindCallApply": true,           //严格的bind/call/apply检查
    // "noImplicitThis": true,                //不允许 this 有隐式的any类型
    

    /* Additional Checks */
    // "noUnusedLocals": true,                //检查只声明, 未使用的局部变量
    // "noUnusedParameters": true,            //检查未使用的函数参数
    // "noImplicitReturns": true,             //每个分支都要有返回值
    // "noFallthroughCasesInSwitch": true,    //防止switch 语句贯穿
    "esModuleInterop": true,                  //允许 export = 导出, 由import from 导入
    // "allowUmdGlobalAccess": true,          //允许在模块中访问UMD全局变量
    // "moduleResolution": "node",            //模块解析策略
    // "baseUrl": "./",                       // 解析非相对模块的基地址
    // "paths": {                             // 路径映射, 相对于 baseUrl
    //    "jquery": ["node_modules/jquery/dist/jquery.slim.min.js"]
   // },    
   // "rootDirs": ["src", "out"],              //将多个目录放在一个虚拟目录下,用于运行时          
    //"ListEmittedFiles": true                  // 打印输出文件
    //"listFiles": true                        //打印编译的文件(包括引用的声明文件)
  }
}

typescripts 的数据类型

  • boolean

        let a: string = true
    
  • number

        let b: number = 1
    
  • string

      let c:string = 'hello
    
  • array

    let f: Array<number | string > = [1 , '123']
    let f2: any[] = [ 1, true, 'hello]
    
  • tuple (元祖)

    元祖类型的定义: 有指定的数据类型元素, 不能进行数组越界操作

     let d: [boolean, string, number] = [true, 'hello', 1]
    
  • enum (枚举)

    枚举类型分为常量枚举 和 普通枚举, 这两个区别是 常量枚举会在编译阶段被删除并且不能包含计算成员 , 若包含则直接报错

    enum Gender = {
        BODY,
        GIRL
    }
    const enum Colors = {
        YEKKOW,
        GREEN,
        BLACK
    }
    
  • interface (接口)

    interface Week{
        (response: string): void;
        name: string;
        age: number;
        status: boolean
        [x: string]: any
    }
    
  • null 和 undefined

    null 和 undefined 是其他类型的子类型, 可以赋值给其他类型 ,如数字类型,但是不可以直接赋值, 如果想赋值的话 以下有两种解决办法
    方法一: 通过联合类型 let x: number | null | undefined; 方法二: 在tsconfig.json 文件里面设置 strictNullChecks 为 false, 则不会对这种情况进行检查

      let x: number = 2
      x = null
      x = undefined
    
  • any

    代表类型 是任何类型, 当类型很复杂的情况下可以使用any

  • void

    没有任何类型, 当一个函数没有返回值的时候, 则认为他的返回值是 void 类型, 当我们声明一个变量为void 时, 在非严格模式下可以被赋值为undefined 和 null

  • nerver

    never是一个不包含值的类型, 拥有void 返回类型的函数能正常运行, 但是拥有never 返回值的类型无法正常返回, 无法终止, 或会抛出异常

类型推论

函数定义的几种方式

* function ab1(x: number, y: number):number { retrun x + y}
    * let ab2: (x: numberm y: number) => number
    * type ab3: (x:number, y: number) => number
    interface ab4 {
        (x: number, y: number): number
    }

可选参数

当实参的参数数量 小于 形参的参数数量的时候, 我们可以用? 来使用 可选参数, 但是可选参数一定要在必传参数的后面

    function print(name: string, age?: number):string {
        return name
    }
    print('hello')
    print( 'hello', '2' )

默认参数

如果不传参数则为undefined, 最后面的形参是默认参数 则可以不用传

function print(name: string, age:string= 'methods', url: string='/post'): string {
    return name + age + url
}
print('hello', undefined)
print(hello)

剩余参数

剩余参数 和 es6 里面的方法是一样的 通过...来实现的, 剩余参数在所有参的后面

function print(x: number, ...y: number[]): any {
    return x + y.reduce( (pre,cur) => pre + cur, 0 )
}

函数重载

函数重载指的是有两个或者两个以上的相同函数名的函数, 参数类型不一样, 给一个函数提供多种类型定义,母的就是将参数约束为number 和 string, 所以最后实现的不是重载,而是遵循前面的声明

let obj: any = {}
function attr(val: string): string;
function attr(val: number): number;
function attr(val: any): void {
    if(typeof val === 'string') {
        obj.name = val
    } else if( typeof val === 'number' ) {
        obj.age = val
    }
}
attr('zfpx')
attr(9)
attr(true)

类成员

ts 与js 不同的地方是实例的属性必须要有初始值, 或者在函数中被初始化, 通过public 来进行修饰的则不一定需要初始值, 如果要关闭初始值的话, 则需要在tsconfig配置里面将strictPropertyInitialization改为false

class Dog {
    name: string;
    age: number = 10;
    myName: string
    constructor(name:  string) {
        this.name = name
        this.myName = name
    }
}

修饰符

  • public

    公有成员, class类 默认为public 类里面 子类 其他任何地方都可以去访问

  • protected

    受保护成员 只能在类 或者 子类中才能使用, 不能在实例中调用, 如果constructor上有protected,代表这个类不能被实例化, 只能被继承

  • private

    私有成员 只能在类或者子类中使用, 不能在实例中调用, 只能在class 中调用

  • readonly

    只读属性, 不能以去修改

  • static 静态属性, 只能通过类名来调用
class Father {
    public name: string;  
    protected age: number; 
    private money: number;
    constructor(name:string,age:number,money:number) {//构造函数
        this.name=name;
        this.age=age;
        this.money=money;
    }
    getName():string {
        return this.name;
    }
    setName(name:string): void{
        this.name=name;
    }
}
class Child extends Father{
    constructor(name:string,age:number,money:number) {
        super(name,age,money);  //super代表父类的实例, 这里必须要先定义, 否则无法使用this , 原因是这里的super是调用父类的构造函数来初始化Child, 等价于Father.call(this, ...), 也就是绑定相关属性到this之后才能使用
    }
    desc() {
        console.log(`${this.name} ${this.age} ${this.money}`);
    }
}

let child = new Child('zfpx',10,1000);
console.log(child.name);
console.log(child.age);
console.log(child.money);

抽象类 ( abstract)

定义: 描述的是一种抽象的概念, 不能被实例化, 只能被继承, 无法创建抽象类的实例, 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现, 而且必须实现

abstrace class Animal3 {
    name: string;
    abstract speak()
}

class Cat extends Animal3 {
    speak () {
        console.log('cat 喵喵猫')
    }
}
let cat = new Cat();
cat.speak();

抽象类 vs 接口

> 不同类之间的公有办法和属性 可以抽象成一个接口
> 而抽象类是提供其它类继承的基类, 无法被实例化, 而抽象类中的抽象方法必须在子类中是实现
> 抽象类本身是无法被实例化的, 但是能够实现方法和初始化方法, 但是接口则不行, 接口只能用于描述, 不能提供方法的结过, 也不能为属性进行初始化
> 一个类只能继承一个类或者抽象类, 但是可以实现(implements)多个接口
> 抽象类 也可以提供接口

接口(interface)

定义:
* 接口就是把一些类的公有方法 和属性给抽象出来, 用来约束实现此类的接口
* 一个类可以继承一个类并实现多个接口
* 接口像插件一样可以用来增强类的
* 一个类可以实现多个接口,一个接口也可以被多个类实现, 但是一个类可以多个子类, 但是只能有一个父类

接口的用法

interface Speakable {
    speak(): void;
    name?: string
    readonly id: number
    [x: string]: any
}

接口的实现

    interface Boy {
        name: string,
        eat(): void 
    }
    interface Girl {
        age: number
    }
    class Child implements Boy, Girl {
        public name:string = 'tom'
        public age: number = 10
        eat () {
            console.log('Person 5 说话')
        }
    }

接口的继承

interface Father {
     speak(): void
}
interface child extends Father {
   speakChinese(): void
}
class Person5 implements Child {
    speak () {
        console.log('Person 5')
    }
    
    speakChinese () {
        console.log('speakChinese')
    }
}

函数类型接口

interface discount {
    (price: number): number
}
let cost: discount = function(price: number) :number {
    return price * 8
}

构造函数的类型

class Animal{
  constructor(public name:string){
  }
}
interface WithNameClass{
  new(name:string):Animal
}
function createAnimal(clazz:WithNameClass,name:string){
   return new clazz(name);
}
let a = createAnimal(Animal,'zhufeng');
console.log(a.name);

类型兼容

当一个类型y 可以被赋值给另一个类型x 的时候我们可以理解为x

类型保护

类型保护其实就是一种表达式, 在它们编译的时候就能通过类型信息, 确保某个作用域内变量的类型, 能够通过关键字判断分支中的类型

关键字 typeof instanceof null in

typeof 用法

function dobule(input: string | number | boolean) {
    if( typeof input === string ) {
        input.toLowerCase()
    } else if(typeof input === 'number') {
        input.toFixed(2)
    } else {
        input
    }
}

instanceof 用法

class Animal {
    public name: string = 'zhufeng'
}
class Bird extends Animal {
    public swing: number = 2
}
function getName(a: Animal) {
    if(a instanceof Bird) {
        a.swing
    } else {
        a.name
    }
}

null 的用法

function getFirstLetter(s: string | null) {
    if( s === null ) {
        s = ''
    } 
    return s.chartAt(0)
}

in 的用法

interface Bird {
    swing: number
}
interface Dog {
    leg: nubmer
}
function getNumber(x: Bird | Dog) {
    if ( 'swing' in x ) {
        return x.swing
    }
    return x.leg
}

自定义类型保护

interface Bird {
    swing: number
}
interface Dog {
    leg: number
}
//没有相同字段可以定义一个类型保护函数
	function isBird(x: Bird | Dog): x is Bird {
		return x.legs == 2;
	}
	function getAnimal(x: Bird | Dog) {
		if (isBird(x)) {
			x.name1;
		} else {
		}
	}
	let x: Bird = { name1: 'Bird', legs: 2 };
	getAnimal(x);

类型变换

交叉类型

取的是两个类型的并集

interface Body {
    name: string
}
interface Girl {
    age: number
}
type Child = Body & Girl
let child:Child = {
    name: 'tom',
    age: 12
}

typeof 获取一个变量

//先定义类型在定义变量
typeof PeoPle = {
    name: string;
    age: number;
    gender: string
}
let p1: People = {
    name: '珠峰'
    age: 10,
    gender: 'male'
}
//先定义变量在定义类型
let p1 = {
    name: 'zhufeng',
    age: 10,
    gender: 'male'
}
type People = typeof p1
function getName(p: People): string {
    return  p.name
}

[] 索引访问操作符

//可以通过 []获取一个类型的子类型
interface Person {
    name: string;
    age: number;
    job: {
        name: string
    };
    interests: {name: string, level: number}[] 
}
let FrontEndJob:Person['job'] = {
    name: '前端工程'
}
let interestLevel: Person['interests'][0]['level'] = 2

keyof 索引类型查询操作符

interface Person = {
    name: string;
    age: nubmer;
    gender: 'mode' | 'commonJS'
}
// 等价于下面 type personKey = 'name' | 'age' | 'gender'
type personKey = keyof Person
function getValueByKey(p: Person, key: PersonKey) {
    return p[key]
}
let val = getValueByKey({name: 'zhufeng', age: 10, gender: 'male'}, 'name')

内置工具模块

Partial , Required, Pick, Readonly

Partial

    // 将必须参数轮询变为可选
    //等价于下面 type Partials<T> = { [key in keyof T]? T[key] }
    interface A = {
        a1: string;
        a2: number;
        a3: boolean
    }
    type aPartail = Partail<A>
    const a: aPartail = {}

Required

    // 将可选参数变为必选参数
    // 等价于下面 type Requireds<T> = { [p in keyof T]-?: T[p] }
    interface Person {
        name: string;
        age: number;
        gender? "male" | 'female'
        let p: Required<Person> = {
            name: 'zhufeng',
            age: 10,
            gender: 'male'
        }
    }

Pick

    // 能过将我们从传入的属性中摘取某一项返回
    // 等价于下面 type Pick<T, k extends keyof T> = {[p in K]: T[p]}
    interface  Animal {
        name: string;
        age: number
    }
    type AnimalSub = Pick<Animal, 'name'> // {name: string}

Readonly

    // 为传入的每一项属性都加上readonly 修饰符来实现
    // 等价于 type Readonly<T> {readonly [p in keyof T]: T[p]}
    interface Person {
        name: string;
        age: number;
        gender?: "male" | "female"
    }
    let p:Readonly<Person> = {
        name: 'zhufeng',
        age: 10,
        gender: 'male'
    }

内置条件模块

Exclude<T, U> // 从 T 可分配给的类型中排除 U。
Extract<T, U> // 从 T 可分配的类型中提取 U。
NonNullable // 从 T 中排除 null 和 undefined。
ReturnType // 获取函数类型的返回类型。
InstanceType // 获取构造函数类型的实例类型。

Exclude

type  E = Exclude<string|number,string>;
let e:E = 10;

Extract

type  E = Extract<string|number,string>;
let e:E = '1';

NonNullable

type  E = NonNullable<string|number|null|undefined>;
let e:E = null;

ReturnType

function getUserInfo() {
  return { name: "zhufeng", age: 10 };
}

// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
type UserInfo = ReturnType<typeof getUserInfo>;

const userA: UserInfo = {
  name: "zhufeng",
  age: 10
};

InstanceType

class Person{
  name:string;
  constructor(name){
    this.name = name;
  }
  getName(){console.log(this.name)}
}

type  P = InstanceType<typeof Person>;
let p:P = {name:'zhufeng',getName(){}};