关于 TypeScript 装饰器和接口的问题,请教一下各位

156 天前
 DreamingCTW

我是做 Java 后端的,这两天正在系统化的学习 ts ,学到装饰器的时候,有点疑惑,希望可以指导下,谢谢大家。以下是 Demo

type Constructor = new (...args: any[]) => {};

function LogTime<T extends Constructor>(target: T) {
  return class extends target {
    createTime: Date;
    constructor(...args: any[]) {
      super(...args);
      this.createTime = new Date();
    }
    getCreateTime() {
      return `该对象的创建时间是:${this.createTime}`;
    }
  };
}

@LogTime
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

interface Person {
  getCreateTime(): void;
}

const person = new Person("小明", 18);
console.log(person.getCreateTime());

如果不声明 interface Person { getCreateTime(): void },那么 person.getCreateTime()会报错,提示 Person 对象中没有这个属性。加上这个接口就不会报错了,但有一个问题,接口中规范了 getCreateTime()函数的返回值为 void ,实际上装饰器中 getCreateTime()返回的是 string ,这不矛盾吗?我尝试修改接口中的 getCreateTime()函数的返回值,发现改成任意类型都可以...

现在给我的感觉就是,为了让 person.getCreateTime()代码不报错,就打了 interface Person 这个补丁,而且可以不强制遵循这个补丁的规范。

由此我想到了另一个问题,从上述代码看,Person 既是一个 class ,也是一个 interface ,我还可以创建一个新的 class 来 implements Person ,发现 class Person 中的所有属性也都要实现,所以 interface Person 是把 class Person 转成了接口?

class NewPerson implements Person {
  name: string;
  age: number;
  createTime: Date;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.createTime = new Date();
  }
  getCreateTime() {
    return `该对象的创建时间是:${this.createTime}`;
  }
}
1777 次点击
所在节点    TypeScript
9 条回复
X0V0X
156 天前
这就是人们说的 java 味吗
FrankFang128
156 天前
大部分 JSer 不用装饰器
jackge0323
156 天前
这部分可以跳过,基本不用。
sjhhjx0122
156 天前
这种情况还是继承比较好,当然类装饰器也可以规定类长什么样子,这样就可以要求强制继承一个带这个方法的类了
lisongeee
156 天前
ts 认为装饰器不会改变类型,因此你声明的时候类型是啥它就是啥

达到你想要的效果也很简单,将声明改为调用

const Person = LogTime(
class {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
},
);
lanten
156 天前
目前再 TS 中的任何装饰器(类/方法/属性/参数装饰器)都无法更改调用该装饰器目标的原始类型.

由于你的 getCreateTime 方法是在装饰器中添加的, 且在原始类(Person)中没有该方法的签名, 所以 person 实例在类型系统中压根没有 getCreateTime 方法, 报错符合预期.

声明 `interface Person` 是不完美的, 这会破坏类型系统的准确性. (但也不是不行)

我个人认为你这种场景应该使用基类派生(extends), 而不是装饰器, 通常类装饰器需要配合方法装饰器或属性装饰器才能体现其价值
DreamingCTW
156 天前
@lanten #6 我是在哔站看的 ts 视频,里面讲装饰器的时候举的这个例子,当他为了解决 person 中没有 getCreateTime 函数时,就用了 interface ,他用的时候我没觉得有问题,但当他写 getCreateTime 时,声明了 void ,我就发现了这个问题。他假设的场景是,为每一个 class 的实例对象创建时都记录一个创建时间,所以用了装饰器,这样可以很方便的为每一个 class 上写 @LogTime
DreamingCTW
156 天前
@sjhhjx0122 #4 他假设的场景是,为每一个 class 的实例对象创建时都记录一个创建时间,所以用了装饰器,这样可以很方便的为每一个 class 上写 @LogTime ,只是我在学习的过程中发现了有这个问题。
sillydaddy
156 天前
可以给装饰器定义明确的返回类型:

```
interface WithCreateTime {
getCreateTime(): string;
}

function LogTime<T extends Constructor>(Base: T): T & Constructor<WithCreateTime>;
```

上面的 LogTime 函数定义中,明确指定了返回类型:T & Constructor<WithCreateTime> 。并且与 LogTime 实际返回值的类型是一致的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1123060

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX