如何强制一个字段类型与typescipt中泛型类型中的另一个字段类型相同?

问题描述 投票:0回答:1

假设我有该代码:

type MyStr = "one" | "two" | "three";

const x: MyType<MyStr> = { master: "one", slave: "one" }; // should compile
const y: MyType<MyStr> = { master: "one", slave: "two" }; // should not compile

我的目标是创建一个 MyType 类型,强制从站与主站具有相同的类型。

一个解决方案是这样做:

type MyType<T extends string> = {
  [K in T]: { master: K, slave: K }
}[T];

我们的想法是创造所有可能性,拥有所有版本:

type MyType<T extends string> = {
  [K in T]: { master: K, slave: K }
};
// MyType<"one" | "two" | "three"> = {
//   "one": { master: "one", slave: "one" },
//   "two": { master: "two", slave: "two" },
//   "three": { master: "three", slave: "three" },
// }

在索引 T 处采用该类型给出我们选择的类型。

这种方法有效,但是当有很多字段时,打字稿服务器就会过载并死掉。

有更好的选择来实现这一目标吗?

当然,这只是问题的演示,我真正的问题是创建这样的东西:

type ElementFieldType<T, K extends keyof T> = {
  name: K;
  Element: FC<{ value: T[K], onChange: (value: T[K]) => void }>;
}

所以我需要 K 被强制为

name
中的类型和
Element
的值相同,这意味着一旦
name
被设置为某个键,
Element
就被迫成为处理该键的类型钥匙

typescript
1个回答
0
投票

您的

MyType<T>
T 中的 unions
 上分配
,因此
MyType<T1 | T2 | T3>
相当于
MyType<T1> | MyType<T2> | MyType<T3>
。但显然你有一些用例,这对你不起作用,因为
T
中的联合太大了。
MyType<T>
的特殊形式是在 microsoft/TypeScript#47109 中创造的 分布式对象类型。如果您愿意,可以将其更改为分布式条件类型

type MyType<T> = T extends unknown ? { master: T, slave: T } : never;
const x: MyType<MyStr> = { master: "one", slave: "one" }; // okay
const y: MyType<MyStr> = { master: "one", slave: "two" }; // error

但是,如果分布式对象类型对于编译器来说太多,那么分布式条件类型是否有足够的帮助就不清楚了。


如果您无法预先计算给定

T
的所有可能性,那么您将需要添加泛型类型参数,以便仅计算相关可能性:

type MyType<T, U extends T> = { master: U, slave: U };

这里

T
对应的是你原来的工会,而
U
则是你想要支持的成员。这让你可以写

const x: MyType<MyStr, "one"> = { master: "one", slave: "one" }; // okay
const y: MyType<MyStr, "one"> = { master: "one", slave: "two" }; // error

但是现在你已经在类型中写了

"one"

如果你能让编译器通过编写类似 const x: MyType<MyStr, infer> = {⋯} 甚至只是

const x: MyType<MyStr> = {⋯}
的内容来为你
推断
,那就太好了,但它不能。 microsoft/TypeScript#32794 有一个开放的功能请求,但目前这是不可能的,您需要解决它。目前,只有在调用泛型函数时才会推断泛型类型参数,因此通常的解决方法是创建一个辅助函数,该函数仅返回其输入,但它会为您提供所需的推断。 但即使对于泛型函数,您也不能手动指定一种类型参数(如

MyType

)并让编译器推断出不同的类型参数。要么全有,要么全无。 TypeScript 需要按照

microsoft/TypeScript#26242
中的要求进行部分类型参数推断,但同样,目前这是不可能的,您需要解决它。这里通常的解决方法是对函数进行 curry,这样你就可以得到 <T, U extends T>(m: MyType<T, U>) => MyType<T, U>,而不是
<T>() => <U extends T>(m: MyType<T, U>) => MyType<T, U>
const myType = <T,>() => <U extends T>(m: MyType<T, U>) => m;

然后您可以指定 
T

并将结果保存到新函数中:

const myTypeMyStr = myType<MyStr>();

并使用它:

const x = myTypeMyStr({ master: "one", slave: "one" }); // okay const y = myTypeMyStr({ master: "one", slave: "two" }); // no error!! 💥

等等,它不起作用。为什么不呢?

问题在于,TypeScript 非常乐意推断
"one" | "two"

内部

T
的并集
myTypeMyStr
,因此也推断在
myType
内部。避免
that
的方法是告诉编译器您可以从 T 推断出
master
,但不能从
slave
推断出 。实现
那个
的最简单方法是NoInfer实用程序类型
type MyType<T, U extends T> = { master: U, slave: NoInfer<U> };

现在其他一切都按预期工作:

const myType = <T,>() => <U extends T>(m: MyType<T, U>) => m; const myTypeMyStr = myType<MyStr>(); const x = myTypeMyStr({ master: "one", slave: "one" }); // okay const y = myTypeMyStr({ master: "one", slave: "two" }); // error

就这样吧。它可以工作,但不幸的是,无缝支持它所需的功能尚未实现,而且解决方法有些笨拙。但这是你现在能做的最好的事情了。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.