打印脚本:带自定义方法的工厂-第三步自定义、第三步、脚本、工厂

2023-09-03 13:35:26 作者:天各一方各自天涯

我在一家工厂工作;我最终需要向this answer和this answer添加自定义方法,我们能够使其工作几乎如预期的。

几乎是因为它只适用于没有任何必需参数的方法;如果我们尝试添加至少具有一个必需参数的方法,则会收到编译错误。

如何用按键精灵制作出自定义脚本

我尝试将REST参数数组同时添加到method参数和M类型的声明中(见下文),但仅在调用方法时有用。

(this: E & S, ...args: unknonwn[]) => unknown

type Base = { id: string }
type Factory<E> = new () => E;

function factory<E extends Base>(name: string): Factory<E>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S, ...args: unknown[]) => unknown>>(
  name: string, methods: M & Record<string, ((this: E & M, ...args: unknown[]) => void)>
): Factory<E & M>;
function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S) => unknown>>(
  name: string, methods?: M
): Factory<E> {
  const ret = function (this: Base) {
    this.id = "0"
  };

  Object.defineProperty(ret, "name", { value: name });

  if (methods) for (const method in methods) Object.defineProperty(ret.prototype, method, { value: methods[method] });

  return ret as unknown as Factory<E>;
}

const T1 = factory("T1");
const t1 = new T1();
console.log(t1, t1.id);

const T2 = factory(
  "T2",
  {
    foo: function (repeat: boolean) {
      const ret = ! repeat;
      if(repeat) this.foo(ret);
      return ret;
    }
  },
);
const t2 = new T2();
console.log(t2, t2.id, t2.foo(true));

这里有一个playground可供实验。

推荐答案

请考虑以下代表您的用例的示例:

const foo = (fn: (a: unknown) => unknown) => fn

foo((arg: number) => arg) // error

错误: Type 'unknown' is not assignable to type 'number'

请参阅unknown docs:

任何内容都可以分配给unknown,但unknown只能分配给它自己和任何没有类型断言或基于控制流的范围的对象。

那么,如果Anything is assignable to 未知``,为什么会出现错误?

查看:

const bar = (a: unknown) => a
bar(42) // ok

看起来这里出了点问题。不,不是的。这是因为contravariance。在第一个示例中,参数a处于逆变量位置。这意味着继承之箭指向相反的方向。请参阅this答案和this答案。

只需将unknown更改为any即可。别担心,它不会给您的代码带来不安全。事实上,您对methods没有任何特定限制。

解决方案:

type Base = { id: string }
type Factory<E> = new () => E;

function factory<E extends Base>(name: string): Factory<E>;

function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S, ...args: any[]) => any>>(
  name: string, methods: M & Record<string, ((this: E & M, ...args: any[]) => void)>
): Factory<E & M>;

function factory<E extends Base, M extends Record<string, <S extends M>(this: E & S, ...args: any[]) => any>>(
  name: string, methods?: M
): Factory<E> {
  const ret = function (this: Base) {
    this.id = "0"
  };

  Object.defineProperty(ret, "name", { value: name });

  if (methods) for (const method in methods) Object.defineProperty(ret.prototype, method, { value: methods[method] });

  return ret as unknown as Factory<E>;
}

const T1 = factory("T1");
const t1 = new T1();
console.log(t1, t1.id);

const T2 = factory(
  "T2",
  {
    foo: function (repeat: boolean) {
      const ret = !repeat;
      if (repeat) {
        this.foo(ret);
      }
      return ret;
    }
  },
);
const t2 = new T2();
console.log(t2, t2.id, t2.foo(true));

Playground