structuredClonelodash.cloneDeep不能克隆函数.

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

我试了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)

https://www.typescriptlang.org/play?ts=4.9.0-dev.20221003&ssl=15&ssc=2&pln=1&pc=1#code/MYGwhgzhAEAqCmEAuBvAUNT1QHsB28AjABQCU6Wl0ATvEgK7V7Yj7wCCeAnkgBYCWeAOYAhekgBi9PMCT98JPvwikMWAL5rMuAgCYyFKploMmLNpx4DhYydNny8+pSq3RNmtADN7c-OYJLJRtxKRk-PEIAHlgAPmIcACMAK3hZAC5oAFEAD1B6ABN4GIAaaBQAbQBreC5M5GpBIQAfPHoAW0T4amaILk6cEABdTLBuMrAAB0mQOugxrjLgMBAQUe51WNJM2ENjOkZmBvpZRngCgGFWAgSUtKRSTx9wxwCObmDRUN9HXRj4pKpDLZPIgQrFWBlMIOfBbHZ7GgHMzHU60S7XeC3IEPNDqIA

有什么办法可以解决这个问题吗?

谢谢

推荐答案

如果TypeScrip有在microsoft/TypeScript#29317中实现(但从未合并)的negated types种类型,那么您只需编写

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

然后就完事了.但在TypeScrip中没有not,至少在TypeScrip4.8中是这样的,所以你不能.


存在try 模拟/仿真not的各种方式.一种方法是做你正在做的事情:写一个conditional type,就像一个圆形generic constraint,你已经这样做了:

declare function cloneAnythingButFunction<T>(object: Exclude<T, Function>): T;

这适用于specific种类型的参数,例如

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

但当参数本身是generic类型时,它可能会崩溃.类方法中thispolymorphic this type是隐式泛型类型参数.(也就是说,它被视为类实例类型的某个未知类型constrained).编译器不知道如何验证thisExclude<this, Function>的赋值,这是有意义的,因为编译器不知道如何说Test的某个子类型可能不也实现Function.

您可以通过将this扩展为特定的超类型来解决此问题,如Test:

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

另一种近似否定类型的方法是将所有可能类型的集合分割成mostly个覆盖要否定的事物的补码的部分.我们知道没有基元类型是函数,所以我们可以从

type NotFunction = string | number | boolean | null | undefined | bigint | ....

然后我们可以开始添加同样不是函数的对象类型.可能任何类似数组的类型也不是函数:

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | ...

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

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | { apply?: never, [k: string]: any } | ...

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

type NotFunction = string | number | boolean | null | undefined | bigint | 
   readonly any[] | { apply?: never, [k: string]: any } | 
   { call?: never, [k: string]: any };

我们应该继续前进,还是停在那里?上述NotFunction的定义将对同时具有apply属性和call属性的任何对象进行错误分类.我们担心这些吗?我们可能会遇到具有名为applycall的属性的非函数对象吗?如果是这样的话,我们可以增加更多的片段.也许我们想添加没有bind属性的对象.或具有bindcallapply属性的对象,但其中每个属性本身都是原语,如{ 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
    }
}

它们的行为符合人们的期望.仍然是possible,Test的某个子类型可以赋值给Function,但是编译器不在乎,我想我们也不在乎.

当然,我们并不关心这个:

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

因为如果我们这样做了,我们将不得不在我们的大约NotFunction的基础上增加更多的????数字来处理它.


Playground link to code

Typescript相关问答推荐

接口的额外属性作为同一接口的方法的参数类型'

TypeScript:在作为参数传递给另一个函数的记录中对函数的参数实现类型约束

带占位符的模板文字类型何时扩展另一个?

如何在LucideAngular 添加自定义图标以及如何使用它们

在包含泛型的类型脚本中引用递归类型A<;-&B

是否有一个版本的联合类型递归地使属性只出现在一个可选选项中?

如何使默认导出与CommonJS兼容

布局组件中的Angular 17命名路由出口

尽管对象的类型已声明,但未解析的变量<;变量

TypeScrip:强制使用动态密钥

类型脚本-处理值或返回值的函数

如果判断空值,则基本类型Gaurd不起作用

如何键入对象转换器

如何使对象同时具有隐式和显式类型

TS2532:对象可能未定义

为什么我的 Typescript 函数中缺少 void 隐式返回类型?

通用可选函数参数返回类型

显式推断元组类型

TypeScript:可空类型定义的引用子类型

TypeScript 方法包装另一个类的任何方法