TS使用字段推导回调标识及参数类型

type Watcher = {
  on(
    eventName: string,
    callback: (oldValue: any, newValue: any) => void
  ): void;
};
function watch(obj: object): Watcher;

function watch(obj){
  function on(eventName, callback) {
  }
  return {
    on: on,
  };
};
const ageSymbol = Symbol("age");
const userWatch = watch({
  name: "ray",
  [ageSymbol]: 18,
  job: "frontend",
  experience: 7,
});
userWatch.on(
  "nameChanged",
  (oldValue,newValue) => {
    // todo
  }
)

上面代码定义了一个watch函数,此函数接收一个对象且有一个on方法,on方法接受两个参数eventName&callback,我们使用了type Watcher对这个函数的使用进行了约束。
但是这样的约束无疑是毫无意义的,首先我们对于eventName只进行了简单的string,对于callbackoldValuenewValue的类型根本就没有起到ts的意义。下面我们将使用ts将上面的代码改动的更规范

约束eventName

对于eventName的约束,我们期望的类型应该是仅接受传入的对象的key,由于是订阅,因此我们可以自定义一些解释性字符,下面我们使用${key}Changed对于eventName进行约束

type Watcher<T> = {
  on(
    eventName: `${keyof T & string}Changed`,
    callback: (oldValue: any, newValue: any) => void
  ): void;
};
function watch<T extends object>(obj: T extends any[] ? never : T): Watcher<T>;

我们引入范型使用keyof T获取到传入的对象的key,由于我们仅监听为stringkey,因此使用交叉类型将非stringkey进行过滤,然后使用模版字符串将其与Changed拼接。如下图所示,就得到我们所期望的eventName

  • T extends any[] ? never : T:代表接收一个非数组的对象
    在这里插入图片描述

约束callback的参数类型

在这里插入图片描述

如上图所示,对于callback的参数类型根本就没有任何判断。我们应该期望的是,对于我们传入的对象的某个key进行订阅时,我们期望得到它的原始数据类型,当key的值类型为number类型时,我们在使用js原始number方法时编辑器可以智能提示,当值的类型为string类型时,我们在使用js原始number方法时编辑器可以智能提示,等等。总之,我们期望在callback中处理其参数使用js的原始方法更得心应手。

type Watcher<T> = {
  on<K extends string & keyof T>(
    eventName: `${keyof T & string}Changed`,
    callback: (oldValue: T[K], newValue: T[K]) => void
  ): void;
};

在这里插入图片描述
在这里插入图片描述
从上面的代码可以看出,当订阅name时,callback的参数类型推断为string;当订阅experience时,callback的参数类型推断为number。这样显然达到了对于callback参数的类型约束,那这样就结束了吗?我们接着往下看

对于eventNamecallback的双重约束

在这里插入图片描述

我们期望的是eventName应该为${订阅key}Changed,从上面的图我们可以看出,我们订阅的是name,但eventName却为jobChanged,这显然不是所期望的。

type Watcher<T> = {
  on<K extends string & keyof T>(
    eventName: `${K}Changed`,
    callback: (oldValue: T[K], newValue: T[K]) => void
  ): void;
};

很显然,当我们获取类型断言的T[K]时,其中K为我们真正想要订阅的key,因此我们直接对eventName的约束改为K即可,如下图所示

在这里插入图片描述
在这里插入图片描述
如上图所示,当我们输入错误的eventName时,编辑器会自动给出错误提示。当我们输入正确的订阅key时,编辑器也会提示出正确的eventName。这显然是我们所期望的!而且当我们使用上面的类型约束时,其实也无须书写订阅的key了,当我们给出正确的eventName时,代码会自动推导出你想要订阅的key,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
附完整代码

type Watcher<T> = {
  on<K extends string & keyof T>(
    eventName: `${K}Changed`,
    callback: (oldValue: T[K], newValue: T[K]) => void
  ): void;
};
function watch<T extends object>(obj: T extends any[] ? never : T): Watcher<T>;

function watch(obj) {
  var events = {};
  function on(eventName, callback) {
    if (!events[eventName]) {
      events[eventName] = [];
    }
    events[eventName].push(callback);
  }
  return {
    on: on,
  };
}

推荐文章

  • 57
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值