从概念上讲,您的toggle()
函数可以简单地输入为:
const toggle = <T, U>(initialValue: T | U, options: readonly [T, U]) => {
return [initialValue, options] as const;
}
这里有两个generic类型参数T
and U
对应于第一和第二成员options
tuple(标记为readonly tuple这实际上是less比普通读写元组有限制)。以及类型initialValue
is the union of T
and U
.
这将捕获您输入类型中的主要错误:
toggle(3, ["abc", true]); // error!
// --> ~ Argument of type '3' is not assignable to parameter of type 'string | boolean'.
// toggle<string, boolean>(...)
Here T
被推断为string
, and U
被推断为boolean
,和输入3
不匹配string | boolean
.
但不幸的是,由于 TypeScript 中类型推断的工作方式,它不会将以下内容检测为错误:
toggle("oops", ["light", "dark"]); // no error
// toggle<string, string>(...)
毕竟,T
is string
and U
is string
, and "oops"
也是一个string
。但你希望编译器处理"light"
, "dark"
, and "oops"
, 作为字符串文字类型, 以便"light"
属于类型"light"
,这不兼容"oops"
.
TypeScript 编译器使用启发式方法来推断值的类型。当它看到值时{name: "jon"}
,它倾向于推断{name: string}
, 假如说"jon"
只是一个属性的初始化器,它可能需要任何string
价值。这往往是人们想要的。但有时并非如此。有时人们希望将整个值视为不可变的尽可能,因此类型应该是specific尽可能。
在这些情况下,您可以使用const断言告诉编译器:
let v = "light"; // string
let w = "light" as const; // "light"
let x = { name: 'jon' }; // { name: string }
let y = { name: 'jon' } as const; // { readonly name: "jon" }
如果我们使用const
调用时对两个输入进行断言toggle()
,事情会突然按照你想要的方式进行:
toggle("oops" as const, ["light", "dark"] as const); // error!
// --> ~~~~~~~~~~~~~~~
// Argument of type '"oops"' is not assignable to parameter of type '"light" | "dark"
const [v1, o1] = toggle("light" as const, ["light", "dark"] as const);
// v1: "light" | "dark"
const [v2, o2] = toggle({ a: 456 } as const, [{ a: 456 }, { b: 789 }] as const)
// v2: { readonly a: 456 } | { readonly b: 789 }
const [v3, o3] = toggle(true as const, [true, false] as const);
// v3: boolean
const [v4, o4] = toggle({ name: "jon" } as const, [{ name: "jon" }, { name: "amy" }] as const);
// v4: { readonly name: "jon" } | { readonly name: "amy" }
这很好,但它依赖于caller of toggle()
用一个const
断言。
如果你能的话那就太好了实施 toggle()
以这样的方式,通用推论T
and U
类型参数可以是“const
-asserted”,这样调用者就不必写as const
如果他们将文字传递给toggle()
.
不幸的是,没有简单的方法可以做到这一点。不久前我提交了微软/TypeScript#30680请求对此的支持,但尚不清楚何时或是否会实施。
目前,您可以使用一些技巧来获得类似的行为,但它们并不漂亮。如果您有泛型类型参数X extends string
,它会倾向于推断字符串文字类型X
. And X extends number
将对数字文字执行相同的操作。所以X extends string | number | boolean
将推断字符串文字、数字文字和布尔文字。但如果你希望在嵌套级别推断这些,你需要类似的东西X extends string | number | boolean | {[k: string]: X}
。如果您想推断元组类型而不是无序数组,则您的推断域中还需要一些元组类型,所以也许X extends string | number | boolean | [] | {[k: string]: X}
。而且您不想禁止其他类型,因此您需要在其中包含其他内容,例如null
and object
。理想情况下你想包括the unknown type因为它允许一切,但这会抛弃所有暗示。所以你需要定义一个Narrowable
类型是like unknown
除非它可以用来缩小到文字。
这给了你这个:
type Narrowable = string | number | boolean | symbol | bigint
| null | undefined | object | {} | [] | void;
const toggle = <
T extends Narrowable | { [k: string]: T },
U extends Narrowable | { [k: string]: U }
>(initialValue: T | U, options: [T, U]) => {
return [initialValue, options] as const;
}
让我们看看它是否有效:
toggle("oops", ["light", "dark"]); // error
const [v1, o1] = toggle("light", ["light", "dark"]);
// v1: "light" | "dark"
const [v2, o2] = toggle({ a: 456 }, [{ a: 456 }, { b: 789 }])
// v2: { a: 456 } | { b: 789 }
const [v3, o3] = toggle(true, [true, false]);
// v3: boolean
const [v4, o4] = toggle({ name: "jon" }, [{ name: "jon" }, { name: "amy" }]);
// v4: { name: "jon" } | { name: "amy" }
看起来不错。当您使用时,它的行为与原始版本非常相似as const
,除了它不是推断readonly
对象的属性...您可能一开始并不真正关心。
Playground 代码链接