拓展 - typescript笔记

学习 TypeScript 笔记,仅记录一些容易混淆的知识点

数据类型

void 空值

可以表示没有任何返回值的函数

function alertName(): void {
    alert('My name is Tom');
}

也可以用于表示只能被设置成 null 或 undefined 的变量

let unusable: void = undefined;

Null/Undefined

null 和 undefined 是所有类型的子类,也就是说 null / undefined 类型的变量能够赋值给 number 类型的变量

let num: number = undefined;
// or
let u: undefined;
let num: number = u;

类型推论

当没有明确指定类型时,会在编译时进行推断,例如给一个变量赋值字符串,那么 ts 就会推断为 string 类型

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

联合类型

联合类型表示取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

当 ts 无法确定一个联合类型的变量是哪个类型的时候,就会使用类型推断来确定其类型,在没有进行推断之前,只能访问联合类型的所有类型里的共有属性和方法,下例中因为 number 没有 length 方法,所以 ts 报错

function getLength(something: string | number): number {
    return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

接口

在 TS 中使用 interface(接口)去定义对象的类型。

可以通过readonly去定义只读属性,TS 会在修改只读属性时报错。

interface Person {
    readonly id: number;
    name: string;
}

接口之间可以互相继承:

interface Alarm {
    alert(): void;
}

interface LightableAlarm extends Alarm {
    lightOn(): void;
    lightOff(): void;
}

有点特别的是,接口可以继承类:

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

interface PointInstanceType {
    x: number;
    y: number;
}

// 等价于 interface Point3d extends PointInstanceType
interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

函数类型

函数的类型定义要区分 函数声明 和 函数表达式。

函数声明,定义好入参和返回的类型即可:

function sum(x: number, y: number): number {
    return x + y;
}

函数表达式的类型定义要比函数声明来的复杂,定义同一个函数:

let sum: (x: number, y: number) => number = function (x, y) {
    return x + y;
};

当然也可以使用接口去定义:

interface Sum {
    (x: number, y: number): number;
}

let sum: Sum;
sum = function(x: number, y: number) {
    return x + y;
}

类型别名

这是之前我一直和 interface 混淆的概念,类型别名用来给一个类型起新的名字,常用于联合类型:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

这里用到了 type 关键字去定义类型别名,在 TS 中,定义字符串字面量类型用的也是 type 关键字:

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

断言

断言,即手动指定一个值的类型。

断言的使用有一定的限制,如果想让 A 与 B 能兼容,那么 A 能被断言成 B,B 也能被断言成 A。

举个例子,下面的 Animal 和 Cat 的关系可以看做是 Cat extends Animal,Cat 能和 Animal 兼容,所以他们能互相断言。

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

function testAnimal(animal: Animal) {
    return (animal as Cat);
}
function testCat(cat: Cat) {
    return (cat as Animal);
}

下面列举了3种常见的使用场景:

常遇见的一个状况就是在联合类型中,只能使用所有类型共有的属性和方法,如果需要使用某个类型的特有方法,就需要使用断言来指定类型:

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function getName(animal: Cat | Fish) {
    return animal.swim(); // 这里会报错
}

// 但使用断言后便不会报错
function isFish(animal: Cat | Fish) {
      return (animal as Fish).swim();
}

还可用于将一个父类断言为更具体的子类:

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};
let tom = animal as Cat;

当使用类型声明去定义 tom 变量时,是会报错的,因为不能将父类的实例赋值给类型为子类的变量:

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};
let tom: Cat = animal; // 此处会报错

还有一种情况就是,TS 会在不存在属性或方法时进行报错,但是有时候我们确定某个方法或者属性不会出错,就可以使用断言去忽略:

(window as any).foo = 1;

枚举

枚举解决了状态判断的痛点,下面是对于状态值判断的一段代码,从代码中很难看出值1对应的状态:

function handleStatusChange(status) {
    if(status == 1){
        // do somthing
    }else if(status == 2){
        // do somthing
    }else if(status == 3){
        // do somthing
    }
}

如果使用枚举,且如果是数字枚举,那么

enum orderStatus {
    UN_PAYED,   // 未支付
    PAYED,      // 已支付
    CANCELED,   // 已取消
}

那么上述的代码就可以修改为:

function handleStatusChange(status) {
    if(status == orderStatus['UN_PAYED']){
        // do somthing
    }else if(status == orderStatus['PAYED']){
        // do somthing
    }else if(status == orderStatus['CANCELED']){
        // do somthing
    }
}

枚举又可以分为数字枚举和字符串枚举。

他们的区别就在于:数字枚举既可以用枚举名称来索引,也可以用枚举值来索引;而字符串枚举只能够通过枚举名称来索引,不能通过枚举值来反向索引。

enum orderStatus {
    UN_PAYED,   
    PAYED,      
    CANCELED,   
}

orderStatus.UN_PAYED // 0;
UN_PAYED[0] // "UN_PAYED";

从他们被编译的结构上,就可以看出区别:

// 数字枚举
enum orderStatus {
    UN_PAYED,   
    PAYED,      
    CANCELED,   
}

var orderStatus;
(function (orderStatus) {
    orderStatus[orderStatus["UN_PAYED"] = 0] = "UN_PAYED";
    orderStatus[orderStatus["PAYED"] = 1] = "PAYED";
    orderStatus[orderStatus["CANCELED"] = 2] = "CANCELED";
})(orderStatus || (orderStatus = {}));

// 字符串枚举
enum orderStatus {
    UN_PAYED = "A",   
    PAYED = "B",     
    CANCELED = "C",   
}

var orderStatus;
(function (orderStatus) {
        orderStatus["UN_PAYED"] = "A";
      orderStatus["PAYED"] = "B";
    orderStatus["CANCELED"] = "C";
})(orderStatus || (orderStatus = {}));

如果在枚举之前加上const,就是常量枚举,不同于常规的枚举,他们在编译阶段会被删除,减少额外开销:

const enum orderStatus {
    UN_PAYED,   
    PAYED,      
    CANCELED,   
}

修饰符

TS 提供了三种修饰符:public、private 和 protected

public:修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的;

private:

  • 修饰的属性或方法是私有的,不能在声明它的类的外部访问;
  • 在子类中也不能访问;
  • 当构造函数用 private 进行修饰时,该类不能被继承或者实例化;

protected:在子类中允许被访问的 private 属性或方法,当构造函数用 protected 进行修饰时,该类只允许被继承;

参数属性

修饰符和 readonly 还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁:

class Animal {
  public name: string;
  public constructor(name) {
    this.name = name;
  }
}

// 可以简写为
class Animal {
  public constructor(public name) {
  }
}

如果需要加上 readonly 只读属性关键字:

class Animal {
  // public readonly name;
  public constructor(public readonly name) {
    // this.name = name;
  }
}

类与接口

interface(接口)通过 implements 进行实现(定义):

// 共有特性
interface Alarm {
    alert(): void;
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}

class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

一个类可以实现多个接口:

interface Alarm {
    alert(): void;
}

interface Light {
    lightOn(): void;
    lightOff(): void;
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

一些特殊的场景

键名

如果要去定义一个对象的键和值的类型,就需要如下操作:

interface DataInterface = {
    query?: { [key: string]: number | string };
}

Promise

如果要定义 Promise 的 resolve 和 reject 的值的类型,可以在创建 Promise 时在后面跟上泛型,例如:

function foo(){
    return new Promise<number>((resolve,reject)=>{
        resolve(123)
    });
}

定义Ref

在 react 中定义 ref 时,可以使用 RefObject<> 的形式定义,例如定义一个 inputRef:

const inputRef: RefObject<HTMLInputElement> = useRef(null);
作者

BiteByte

发布于

2020-12-10

更新于

2024-02-23

许可协议