Typescript 基础语法总结

342 阅读10分钟

基本类型

  1. string
    • 语法: let str: string = 'string';
  2. number
    • 语法:let number: number = 123;
  3. boolean
    • 语法:let boolean: boolean = true;
  4. 元组
    • 语法:tuple: [string, number] = ['1', 2];
    • ts新增的类型,是一种特殊的数组,被限定了数组内成员的个数和类型。
    • 元组越界:

      let tuple: [number, number] = [1, 2];
      tuple.push(3);  // 元组是可以通过push添加元素的
      tuple[2];  // Error, 但是元组无法访问push添加的元素
  5. 数组
    • 语法:

      let arr: number[] = [1,2,3]; 
      // 通过「泛型接口」定义数组, 联合类型可以声明多种数组成员类型。
      let arr2: Array<number | string> = [1,2,3,'4'];  
  6. symbol
    • 语法:let symbol: symbol = Symbol();
  7. object
    • 语法:

      let obj: object = {};
      obj.x = 'x';  //Error, 因为object没有定义具体属性,所以不能访问或修改属性。
      
      let obj2: {x: number, y: number} = {x: 1, y: 2}; // 声明具体属性
      obj2.x = 3;

  8. undefined / null
    • 语法:

      let undefined1: undefined = undefined;
      let null1: null = null;

    • undefined / null 是一切基本类型的子类型,其他基本类型可以赋值成null / undefinednull / undefined 不能赋值给其他基本类型。
  9. void
    • 语法:function fn(x: number, y: number): void {}
    • 表示没有任何返回值,通常用于表示函数没有任何返回值。
    • 注意:在js中void是一个操作符,它可以让任何表达式返回undefined。
  10. never
    • 语法:

      function fn(): never {throw new Error('error')};
      function fn2(): never {while(true){}};

    • 表示永远不会有返回值,通常用于表示两种类型函数内抛出错误(Error)、函数内部存在死循环(while(true))。
  11. any
    • 语法:let any: any = '222';
    • 最宽泛的类型,可以表示任意的类型。

枚举

声明方式:

emun E { 
    X,  // 如果第一个成员没有初始化的值,默认为0
    Y
}

枚举成员的类型:

  • 数字枚举成员

    // 如果有枚举成员初始化为数字常量,那之后的枚举成员的值会根据默认值递增
    enum E {
        X,      //0
        Y = 2,  //2
        Z       //3    }

  • 字符串枚举成员

    // 使用字符串初始化的枚举成员,之后的枚举成员必须赋予初始值
    enum E {
        X: 'X',
        Y: 'Y'
    }

  • 联合枚举成员
    enum E {
        X: 'X',
        Y:  1
    }

  • 计算枚举成员

    enum E {
        X: 'X'
    }
    enum E2 {
        Y: E.X,
        Z: (1+2)/3,
        W: 'string', // Error, 计算枚举成员不可以和字符串枚举成员同时存在
    }

枚举成员可以作为「类型注解」:

enum E {
    X,
    Y
}
let x: E.X = 30;  // 数字枚举成员可以和 number 类型相互兼容。

enum E2 {
    X = 'X',
    Y = 'Y',
}
let x2: E2.X = 'X'; // Error, 字符串枚举成员和string类型不兼容。
let x3: E2.X = E2.X;
let x4: E2 = E2.X;
let x5: E2 = E2;  // Error

枚举类型可以当作值传递:

enum E {
    X,
    Y
}
function fn(obj: { X: number }) {    
    return obj.X;
}
fn(E);  // 枚举类型在编译之后就是js中的对象,此时就相当于传递一个对象。

常量枚举:

普通枚举在ts编译阶段会被编译成不同的对象,而常量枚举在ts编译阶段会被删除,使用枚举成员的地方会被替换成相应的枚举成员的值。

const enum E {
    X,
    Y
}

函数

声明方式:

// 1.在函数体内声明
function fn1(x: number, y: number): number {return x+y};

// 2.在变量中声明, 声明中的变量名可以和函数体内函数名不同
let fn2: (x: number, y: number) => number
fn2(a, b) {return a+b};

// 3.用「类型别名」声明
type fn3Type = (x: number, y: number) => number
let fn3: fn3Type = (a, b) => (a + b);

// 4.用「接口」声明
interface fn4Inter {
    (x: number, y: number): number
}
let fn4: fn4Inter = (a, b) => (a + b)

ts中的函数规则:

  • 形参和实参需要一一对应。

    function fn(x:number, y:number):number {return x+y};
    fn(1);  // Error, 形参中的y没有传入。
    fn(1,2);

  • 函数参数可以有「可选参数」,「可选参数」必须在「必填参数」之后。

    function fn(x: number, y?:number): number {return x+6};
    fn(1);
    
    function fn2(x?:number, y:number): number {return y+6}; //Error

  • 参数可以有默认值(可选参数可以为其设置默认值)。

    function fn(x: number, y?:number=0): number {return x+y};

  • 和es6一样函数参数可以有「剩余参数」。

    function fn(x: number, ...y: number[]) {}
    
    fn(1,2,3);

  • 与es6不同,ts中函数可以重载。

    function(x:number,y:number):number
    function(x:string,y:string):string
    function(x:any,y:any):any {
        // 在最宽泛的版本中实现这个函数
    }

声明方式:与es6中的类声明方式相同

class Animal {
    construtor(name) {
        this.name = name;
    }
    name: string  // 声明类的成员属性类型
    run() {}
}

ts中类的规则:

  • 类的继承

    class Dog extends Animal {
        construtor(name, age) {
            super(name);  // 派生类函数一定要有super的调用,super代表父类的实例。
            this.age = age;    
        }
        age: number
    }

  • 类的成员修饰符(ts对es的扩展)
    • public: 公有成员 public str:string  
    • protected: 受保护成员(只能在类或者子类中访问)protected str: string
    • private: 私有成员(子类、实例都不可以访问)private str: string
    • readonly: 只读成员(只能访问不能修改)readonly str: string
    • static: 静态成员(只能通过类名.属性值访问)static str: string

      注意:constructor 如果被设置了 private、protected会怎么样?

      设置为private, 类即不能被实例化也不能被继承。设置为protected, 类只能被继承不能被实例化。

class Parent {
    public str1: string = 'st1';
    protected str2: string = 'st2';
    private str3: string = 'st3';
    readonly str4: string = 'st4';   
    static str5: string = 'st5';
}
class Children extends Parent {
    construtor() {
        super();
        
        this.str1;  // success
        this.str2;  // success
        this.str3;  // error
        this.str4 = 'str';  // error
        this.str5;  // error
        Parent.str; // success
    }
}
let parent = new Parent();
parent.str1;  // success
parent.str2;  // error
parent.str3;  // error
parent.str4 = 'str';  // error
parent.str5;  // error
Parent.str5;  // success

// constructor private
class Parent {
    private constructor() {}
}
let parent = new Parent(); // error
class Children extends Parent {}  // error

// constructor protected
class Parent {
    protected constructor() {}
}
let parent = new Parent(); // error
class Children extends Parent {}  // success

  • 抽象类(ts对es的扩展,用于实现类的多态)
抽象类通常作为其他类的基类使用,它不可以被实例化,子类可以重写基类中的方法。
抽象类中还可以包含抽象方法,如果被声明为抽象方法,那么它必须在子类中声明。

abstract class Parent {
    abstract str;
    abstract run(): void;  // 抽象方法只在基类中声明,具体实现要在子类中实现
}
class Children extends Parent {
    str: string = 'str';
    run(): string {
        return 'str';
    }
}

接口

接口是ts中的一种用来约束函数、对象、类的结构和类型的方法。

声明方式:

interface A {
    x: string,
    y: number
}

ts中接口的规则:

  • 接口成员属性:只读属性 && 可选属性

    interface A{
        readonly x: string ,  //只读属性
        y?: string,  // 可选属性
    }
    
    // y 作为可选属性,可以不定义
    let obj: A = {
        x: 'string'  
    }
    
    obj.x = 'aaaa';  // error, x 作为只读属性不可以修改

  • 接口索引签名:  使用索引签名约束一类值的类型

    // 字符串索引
    interface A {
        y: number, // error, 其他属性约束的类型应该在索引类型包含的范围之内
        [key: string]: string
    }
    let obj: A = {
        x: 'string'
    }
    
    // 字符串索引
    interface B {
        [index: number]: string
    }
    let arr: B = ['1', '2']

  • 接口约束:
    • 约束对象

      interface InterObj {
          x: string,
          y: number | string,
      }
      let obj: InterObj = {
          x: 'string',
          y:  10
      }

    • 约束函数

      // 不仅可以约束函数,还可以约束函数的属性
      
      interface InterFn {
          (x: number, y: number): number
          doSomething(): void
          version: string
      }
      
      let fn: InterFn = ((x, y) => {return x + y}) as InterFn;
      fn.doSomething = () => {};
      fn.version = '1.0';

    • 约束类

      interface InterClass {
          state: boolean
      }
      // 接口描述静态部分和实例部分
      // 接口在约束类时只能描述实例属性interface ClockInterface {
          // new (h: number, m: number);  Error 构造器属于静态属性
          tick();
      }
      class Clock implements ClockInterface {
          constructor(h: number, m: number) {}
      
          tick() {
              console.log('1');
          }
      }
      
      
      // 类作为参数传递时,可以使用构造器描述类
      interface ClockClassInterface {
          new (h: number, m: number): ClockInterface
      }
      
      function create(ctor: ClockClassInterface, h, m): ClockInterface {
          return new cotr(h, m)
      }
      

  • 继承:

    • 「接口」继承「接口」

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};  // 这样写可以先定义对象类型,再赋值

square.color = "blue";
square.sideLength = 10;

  • 「接口」继承「类」

// 如果接口继承的类中有private、protected属性,当前接口只能被类的子类使用
class Control {
    private state: any;
    protected state2: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl { 
    // 使用接口成功
}

泛型

让函数和类支持多种类型,不预先确定数据类型,具体类型在使用时确定

声明方式:

  • 泛型定义函数

    // 你可以把T当作反省变量,它可以是任意所有类型
    // identity定义了一个接收参数和返回值需要一样的函数
    function identity<T>(arg: T): T {
        return arg;
    }
    
    indentity('string');  // 可以隐式的让ts来判断泛型变量的类型
    <string>indentity('string');  // 还可以显式的直接声明出来泛型变量的类型

  • 泛型定义接口

    // 第一种写法,把泛型对象仍然写在函数上
    interface GenericInentityFn {
        <T>(arg: T): T
    }
    let myIdentity: GenericIdentityFn = (arg) => arg;
    // 第二种写法,泛型接口定义在接口上
    interface GenericIdentityFn<T> {
        (arg: T): T;
    }
    let myIdentity: GenericIdentityFn<number> = (arg) => arg; // 定义在接口上后,使用接口时必须定义泛型变量的类型
    

  • 泛型定义类

    class GenericNumber<T> {
        constructor(arg: T) {}
        zeroValue: T;
        add(x: T, y: T) => T
    }
    new GenericNumber<number>();
    new GenericNumber(20);

泛型约束:

我们使用了泛型变量,但是我们并不知道反省变量有什么属性,我们也就无法使用它的属性,我们使用泛型约束可以去限制传入的泛型变量要符合条件才可以。

interface LogInter5 {    
    length: number;
}
function log5<T extends LogInter5>(result: T): T {    
    console.log(result.length);  //如果没有约束会报错   
     return result;
}

类型检查机制

  • 类型推断

  • 类型兼容

  • 类型保护

// 类型保护
enum Type {    
    Strong,    
    Weak
}
class Java {   
    helloJava() {        
        console.log('hello Java')    
    }
}
class Javascript {    
    helloJs() {        
        console.log('hello JS');    
    }
}

function isJava(lang: Java|Javascript)lang is Java {
    return (lang as Java).helloJavas !== undefined;
}function isJava(lang: Java | Javascript, x?: number|string) {    let lang = type === Type.Strong ? new Java() : new Javascript();
    
    // 1. instanceof 判断
    if(lang instanceof Java) {        lang.helloJava();
    }else {
        lang.helloJavascript();
    }

    // 2. 类型谓词 判断
    if(isJava(lang)) {
        lang.helloJava();
    }else {
        lang.helloJavascript();
    }

    // 3. typeof 基本类型判断
    if(typeof x == 'string') {
        x.length;
    }else {
        x.toFixed();
    }
}

高级类型

  • 交叉类型 和 联合类型

交叉类型是将多个类型合并为一个类型。

interface A {
    x: string
}
interface B {
    y: string
}
let obj: A & B = {
    x:'string',
    y: 'string'
}

联合类型是表示一个值可以是几种类型之一。

interface A {
    x: string
}
interface B {
    y: string
}
let obj: A | B = {
    x:'string',
}
let obj2: A | b = {
    y: 'string'
}

  • 索引类型

索引类型编译器能够检查使用了动态属性名的代码。
    • 索引类型查询操作符:keyof T  的结果为T上已知道公共属性名的联合

      interface Person { 
          name: string
          age: number
      }
      let personProps: keyof Person;  // 'name' | 'age'

    • 索引返回操作符:T[k] 的接口返回T上已知道属性K的类型

      interface Person { 
          name: string
          age: number
      }
      let personProps: Person['name'] // number

    • 具体例子🌰:

      // 返回对应key的值
      function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {    return keys.map(key => obj[key]);}
      
      let obj9 = {a: 1, b: 2, c: 3};
      getValues(obj9, ['a', 'b']);
      
      getValues(obj9, ['c', 'd'])  // Error, 索引类型可以判断出来T中的属性名不存在c和d

  • 映射类型

映射类型是从旧类型中创建新类型的一种方式。

// 把所有属性变成只读, K in 相当于遍历T中的所有类型
type Readony<T> = {
    readonly [K in keyof T]: T[K]
}

// 把所有属性变成可选
type Partial<T> = {
    [K in keyof T]?: T[K]
}

// 过滤部分属性
type Pick<T, K extends keyof T> = {
    [P in K]: T[K]
}

// 创建新属性
type Record<K extends keyof any, T> = {
    [P in K]: T
}

  • 条件类型
有的条件类型会以一个条件表达式进行类型关系检测,从两种类型中选择其中一种。
  • T extends U ? X : Y

    // 条件类型立即被解析的例子
    type TypeName<T> =
        T extends string ? "string" :
        T extends number ? "number" :
        T extends boolean ? "boolean" :
        T extends undefined ? "undefined" :
        T extends Function ? "function" :
        "object";type T0 = TypeName<string>;  // "string"
    type T1 = TypeName<"a">;  // "string"
    type T2 = TypeName<true>;  // "boolean"
    type T3 = TypeName<() => void>;  // "function"
    type T4 = TypeName<string[]>;  // "object"
    
    
    // 条件类型被推迟解析的例子
    interface Foo {
        propA: boolean;
        propB: boolean;
    }
    
    declare function f<T>(x: T): T extends Foo ? string : number;
    
    function foo<U>(x: U) {
        // 这里因为无法确定x的具体类型,所以a的类型为'U extends Foo ? string : number'
        let a = f(x);
    
        // This assignment is allowed though!
        let b: string | number = a;
    }

  • 分布式条件类型 
   (A | B)extends U ? X : Y  会被解析为(A extends U ? X : Y)| (B extends U ? X : Y)