我在一家工厂工作;我最终需要向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