除了函数之外的任何类型的 Typescript 泛型类型

Cli*_*lfe 3 typescript

structuredClone或者lodash.cloneDeep无法克隆函数。

有没有办法从泛型中排除Function类型?

我尝试过object: Exclude<T, {[key: string|number|symbol]: any, apply: any, call: any}>和 ,当对象作为类object: Exclude<T, Function>传入时,两者都会返回类型错误。this

function cloneAnythingButFunction1<T>(object: Exclude<T, {[key: string|number|symbol]: any, apply: any, call: any}>): T{
    return structuredClone(object)}

function cloneAnythingButFunction2<T>(object: Exclude<T, Function>): T{
    return structuredClone(object)
}

// Expect error:
cloneAnythingButFunction1(cloneAnythingButFunction1)
cloneAnythingButFunction2(cloneAnythingButFunction2)

// Expect no error:
class Test{
    clone1(){
        return cloneAnythingButFunction1(this) // error
    }
    clone2(){
        return cloneAnythingButFunction2(this) // error
    }
}
const test = new Test()
cloneAnythingButFunction1(test)
cloneAnythingButFunction2(test)
Run Code Online (Sandbox Code Playgroud)

TypeScript 游乐场

有没有什么办法解决这一问题?

jca*_*alz 5

如果 TypeScript否定了在microsoft/TypeScript#29317中实现的类型(但从未合并),那么你可以写

// Don't do this, it isn't valid TypeScript:
declare function cloneAnythingButFunction<T extends not Function>(object: T): T;
Run Code Online (Sandbox Code Playgroud)

并完成它。但 TypeScript 中没有not,至少从 TypeScript 4.8 开始,所以你不能。


有多种方法可以尝试模拟/模拟not。一种方法是做你正在做的事情:编写一个条件类型,其作用类似于循环泛型约束,你已经在这里完成了:

declare function cloneAnythingButFunction<T>(object: Exclude<T, Function>): T;
Run Code Online (Sandbox Code Playgroud)

这对于特定类型的参数非常有效,例如

cloneAnythingButFunction(123); // okay
cloneAnythingButFunction({ a: 1, b: 2 }); // okay
cloneAnythingButFunction(Test) // error
cloneAnythingButFunction(() => 3) // error
cloneAnythingButFunction(new Test()); // okay    
Run Code Online (Sandbox Code Playgroud)

但是当参数本身是泛型类型时,它就会崩溃。类方法内部的多态this类型是隐式泛型类型参数。this(也就是说,它被视为受限于类实例类型的某种未知类型)。编译器不知道如何验证thisto的可赋值性Exclude<this, Function>,这是有道理的,因为编译器不知道如何说 的某些子类型Test可能也不会实现Function

this您可以通过扩展到特定的超类型来解决它,例如Test

class Test {
    clone1() {
        const thiz: Test = this;
        return cloneAnythingButFunction(thiz); // okay
        // return type is Test, not this
    }
}
Run Code Online (Sandbox Code Playgroud)

近似否定类型的另一种方法是将所有可能类型的集合分割成大部分覆盖您要否定的事物的补集的部分。我们知道没有任何原始类型是函数,所以我们可以从

type NotFunction = string | number | boolean | null | undefined | bigint | ....
Run Code Online (Sandbox Code Playgroud)

然后我们可以开始添加也不是函数的对象类型。也许任何类似数组的类型都不会是函数:

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | ...
Run Code Online (Sandbox Code Playgroud)

任何没有定义apply属性的对象也不是函数:

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | { apply?: never, [k: string]: any } | ...
Run Code Online (Sandbox Code Playgroud)

任何没有定义call属性的对象也不是函数:

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | { apply?: never, [k: string]: any } | 
   { call?: never, [k: string]: any };
Run Code Online (Sandbox Code Playgroud)

我们应该继续前进还是停下来?上面的定义NotFunction将会对任何同时具有apply属性和call属性的对象进行错误分类。我们担心这些吗?我们是否可能遇到具有名为apply和属性的非函数对象call?如果是这样,我们可以添加更多的部分。也许我们想添加没有bind属性的对象。bind或者具有、call和属性的对象apply,但其中每个属性本身都是基元,例如{ call: string | number | ... , apply: string | number | ... }......但在某些时候我们应该停止。对于许多用例来说,用 3.14 近似就足够了,用 3.141592653589793238462643383 近似通常会带来更多麻烦。我们就用上面的定义吧。

无论如何,现在让我们尝试使用NotFunction代替not Function

declare function cloneAnythingButFunction<T extends NotFunction>(object: T): T;

cloneAnythingButFunction(123); // okay
cloneAnythingButFunction({ a: 1, b: 2 });
cloneAnythingButFunction(Test) // error
cloneAnythingButFunction(() => 3) // error
cloneAnythingButFunction(new Test()); // okay    

class Test {
    clone1() {
        return cloneAnythingButFunction(this); // okay
    }
}
Run Code Online (Sandbox Code Playgroud)

这些行为如所期望的那样。的某些子类型仍然有可能Test分配给Function,但编译器并不关心,我认为我们也不关心。

当然我们不关心这个:

cloneAnythingButFunction({apply: "today", call: "1-800-TYP-SCRP"}); // error oh noez
Run Code Online (Sandbox Code Playgroud)

因为如果我们这样做,我们就必须在 的近似值中添加更多的数字NotFunction来处理它。


Playground 代码链接