Skip to content

类的类型理解升级

在typescript中的类有两个点必须要理解

1、在typescript中,类是结构化类型

2、在typescript中,类即声明值也声明类型,而且类的类型有两种:实例化对象类型与类构造函数类型

类是结构化类型

与typescript中的其他类型一样,typescript根据结构比较类,与类的名称无关。

类与其他类型是否兼容,要看结构

如果一个常规对象定义了同样的属性和方法,那么也与类兼容

typescript
class User {
  constructor(
    public id: number,
    public name: string,
  ) {}
  show(addr: string) {
    console.log(addr);
  }
}
class Person {
  constructor(
    public id: number,
    public name: string,
  ) {}
  show(addr: string) {
    console.log(this.id + '---' + this.name + '---' + addr);
  }
}

function desc(user: User) {
  user.show('成都');
}

const u = new User(1, 'ruby');
const p = new Person(2, 'jack');

const a = {
  id: 1,
  name: 'ruby',
  show(addr: string): void {
    console.log('hello ' + addr);
  },
};

desc(u);
desc(p);
desc(a);

不过稍微要注意的是,如果类中有private或者protected修饰的字段,情况就不一样了,typescript会触发检查

类即是值也是类型

typescript
class MyMap {
  state: Record<string, string> = {};

  get(key: string): string | undefined {
    return this.state[key];
  }
  set(key: string, value: string): void {
    this.state[key] = value;
  }
  values(): string[] {
    return Object.values(this.state);
  }
  keys(): string[] {
    return Object.keys(this.state);
  }
  static of(...entries: [string, string][]): MyMap {
    const map = new MyMap();
    entries.forEach((entry) => map.set(entry[0], entry[1]));
    return map;
  }
}

const m1 = new MyMap();
m1.set('id', '1');
m1.set('name', 'jack');
console.log(m1.get('name'));
console.log(m1.values());

const m2 = MyMap.of(['name', 'rose'], ['sex', '女']);
console.log(m2.keys());

注意这句代码:

typescript
const m1: MyMap = new MyMap();

按照上面的写法m1:MyMap中的MyMap很明显是类型,new MyMap()既然已经在操作了,那明显应该是个值。

但是,如果仅仅是这样的话,中间缺失了一环

上面的代码中,m1指MyMap类的一个实例。这个是实例化类型。我们可以通过m1.xxx调用对应的属性和方法

new MyMap()这里既然表示是个值,那么他就应该有值所对应的类型,而且,对于静态方法和静态属性,我们不是还可以通过MyMap.xxx去调用吗?

所以,还有一个类型,表示的是MyMap()这个构造函数的类型

如果你觉得这么描述你不太清楚,我们换一种描述方式:

如果我现在有两个方法,其中一个方法需要的是对象,一个方法是类本身,那这个应该怎么限定?

typescript
class User {
  constructor(
    public id: number,
    public name: string,
  ) {}
  show() {
    console.log(this.id, this.name);
  }
}

function method1(target: User) {
  console.log(target.id);
  console.log(target.name);
  target.show();
}

// 类的构造函数的类型
function method2(target: new (...args: [number, string]) => User) {
  const t = new target(1, 'jack');
}

method1(new User(1, 'jack'));
method2(User);

构造函数类型的表示:

typescript
new (...args:any[]) => any;

但是,我们怎么表示MyMap类自身的构造函数类型呢?可以使用typeof关键字

typescript
type MyMapConstructorType = typeof MyMap;

甚至可以通过typescript给我们预留的工具,通过构造器类型再得到实例化类型:

typescript
type MyMapInstanceType = InstanceType<MyMapConstructorType>;

当然,我们并看不见这个类型中具体有哪些,我们可以模仿着ES关于类的处理,来模拟一下,比如String,Map这些类,大家可以看一下lib.es5.d.ts中的源代码

实例化类型的MyMap类型

typescript
interface MyMap {
  state: Record<string, string>;
  get(key: string): string | undefined;
  set(key: string, value: string): void;
  values(): string[];
  keys(): string[];
}

构造方法类型typeof MyMap

typescript
interface MyMapConstructor {
  new (): MyMap;
  of(...entries: [string, string][]): MyMap;
  readonly prototype: MyMap;
}

所以,对于构造函数类型,我们反过来推导一样成立:

typescript
var m: MyMapConstructor;
type M1 = typeof m;
type M2 = InstanceType<M1>;

如果你觉得MyMap复杂了一些,我们简化一下:

typescript
class Person {
  constructor(public name: string) {}
}
type PersonInstance = InstanceType<typeof Person>;
const person: PersonInstance = new Person('jack');

interface User {
  new (name: string): User;
}
type UserInstance = InstanceType<User>; // User

其实这就对应着我们在vue的代码中,为模板标注类型,可以看到有这样的写法:

typescript
<script>
import {ref} from "vue"
import MyComp from "./components/MyComp.vue";

type MyCompConstructor = typeof MyComp;
type MyCompInstance = InstanceType<MyCompConstructor>;
const myComp = ref<MyCompInstance | null>(null)
// 简写
// const myComp = ref<InstanceType<typeof MyComp> | null>(null)

const openModal = () => {
  myComp.value?.open();
}
</script>

<template>
  <MyComp ref="myComp"/>
</template>

MyComp实际得到的是vue的配置对象,其实就相当于MyComp.vue.vue文件中,我们所写在script中的内容,包括data,methods,生命周期函数等等。

通过typeof MyComp其实得到了vue组件的构造函数类型,然后再通过InstanceType<MyCompConstructor>得到真正的vue实例对象类型,有了这样的类型之后,我们才能在声明的变量中访问到MyComp暴露出来的方法