gpt4 book ai didi

typescript - 键入具有任何类型参数的函数,除非它具有非字符串成员

转载 作者:行者123 更新时间:2023-12-04 01:51:07 26 4
gpt4 key购买 nike

我需要一个函数来接受具有这些限制的参数:

  1. 可以是任何东西(基元、对象、数组……),只要它有一个叫x的成员。 .
  2. 如果它确实有一个叫x的成员它必须是 string 类型.

如何输入这样的函数?

declare function foo<T /* extends ??? */ >(arg: T): boolean

使用条件类型我得到了一些工作,但随后出现了另一个问题。

type Constrained<T> = 'x' extends keyof T
? (T extends { x: string } ? T : never)
: T;

declare function foo<T>(a: Constrained<T>): boolean;

基本上,Constraint<T>解析为 never如果T有一个成员叫x那不是 string 类型或解析为 T否则。然后,任何调用 foo带有“无效”对象的对象将被拒绝,因为无法将任何内容分配给 never (never 本身除外)

它工作得很好......直到我有这样的东西

class SomeClass<U /* extends ??? */> {
prop!: U;

method() {
// Fails :(
// How to restrict U to allow this call?
foo(this.prop); // <-- Error: Argument of type 'U' is not
// assignable to parameter of
// type 'Constrained<U>'.
}
}

playground

最佳答案

除非您确实需要,否则我倾向于远离这样的复杂约束。我的建议是从更原始的部分构建您正在谈论的类型,如下所示:

type Unknown = string | number | boolean | symbol | null | void | object;
type Constraint = Exclude<Unknown, object> | { [k: string]: unknown, x?: string };

那个Constraint只是一个普通的旧联合,它或多或少代表除了具有非 string 的对象之外的所有内容。 x 的值 key 。完美吗?也许不是,但它更容易处理:

declare function foo<T extends Constraint>(a: T): boolean;
class SomeClass<T extends Constraint> { /* ... */ };

foo(undefined); // okay
foo(null); // okay
foo("string"); // okay
foo(123); // okay
foo([]); // okay
foo([123]); // okay
foo([123, { x: "string" }]); // okay
foo(() => 123); // okay
foo({}); // okay
foo({ a: 123 }); // okay
foo({ a: 123, x: 123 }); // error
foo({ a: 123, x: { y: 123 } }); // error
foo(Math.random() < 0.5 ? 1 : { a: 123, x: "string" }); // okay

而且你里面没有问题SomeClass不再:

class SomeClass<T extends Constraint> {
prop!: T;
method() {
foo(this.prop); // easy-peasy
}
}


如果您发现自己迫切需要循环约束或自引用约束,可以安抚编译器,但这对我来说往往是一种反复试验的过程,过程中会遇到很多陷阱。让我们从您的类型函数开始:

type Constrained<T> = 'x' extends keyof T
? (T extends { x: string } ? T : never)
: T;

您的姓名首字母 foo定义似乎有效,但只能通过一些可能有风险的 type inference :

declare function foo<T>(a: Constrained<T>): boolean;

编译器如何知道 T 是什么?鉴于 a类型为 Constrained<T> ?它必须对待Constrained<T>作为 T 的推理站点,通过某种方式看穿条件类型。我猜想编译器看到了 Constrained<T>可分配给 never | T ,即 T , 所以它推断出 Ta 的类型相同.无论如何,那很好。


做这种事情的一种更“官方支持”的方法是制作a类型 T & Constrained<T> , 因为交点是 known to serve as inference sites .这真的和你说的一样,但会让我晚上睡得更香:

declare function foo<T>(a: T & Constrained<T>): boolean;

至于类,你真正做的事情给了你一个循环约束错误:

class SomeClass<T extends Constrained<T>> { /* ... * / } // error!
// Type parameter 'T' has a circular constraint.

这可以通过添加一些虚拟类型参数并使用其评估被推迟到 SomeClass 的条件类型来解决。具体实例化:

class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> { /* ... * / }

declare const ok: SomeClass<{ a: string }>; // okay
declare const alsoOk: SomeClass<{ x: string }>; // okay
declare const notOk: SomeClass<{ x: number }>; // error, number not a string

编译器不再注意到循环,但它仍然存在。

类的实现仍然会给你错误,正是因为你延迟了编译器对循环约束的检查,所以它不知道你在做什么是安全的:

class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: T;
method() {
foo(this.prop); // still error
}
}

一种处理方法是制作 prop类型 Constrained<T>而不是 T :

class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: Constrained<T>;
method() {
foo(this.prop); // okay now
}
}

但是您仍然可能会在其他地方遇到其他此类问题,您最终可能不得不使用 type assertions。消除错误:

class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: T;
method() {
foo(this.prop as Constrained<T>); // I know what I'm doing!
}
}

不管怎样,你可以看到这是多么一团糟。这就是为什么我仍然推荐最初的 plain-old-union 解决方案。

好的,希望对你有帮助。祝你好运!

关于typescript - 键入具有任何类型参数的函数,除非它具有非字符串成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53094090/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com