在 TypeScript 中,是否可以从字符串的输入类型推断出可区分联合的字符串文字类型? [英] In TypeScript is it possible to infer string literal types for Discriminated Unions from input type of string?
问题描述
背景
当前在我的 TypeScript 应用程序中创建事件如下所示:
<预><代码>//这个函数创建事件创建者functiondefineEvent问题
这有效并且很好,但这意味着我需要在定义事件创建者时两次指定事件类型,这在某些方面可能被认为是多余的.
const fooCreator =defineEvent<{type:"foo",//在这里定义类型有效载荷:{富:字符串}}>("foo");//也在这里创建实际的字符串值
是否可以为创建者函数创建一个返回类型,该函数从工厂函数的输入 type
参数中提取字符串文字?
这意味着如果事件创建者是这样提供的,上面的例子仍然有效:
const barCreator =defineEvent<{有效载荷:{酒吧:字符串}}>("酒吧");
尝试失败
我猜我需要合并两种类型,但以某种方式推断字符串文字.
但是这不起作用:
functiondefineEvent(type:ET) {类型 RE = E &{类型:ET};return (payload:E["payload"]) =>{返回 ({类型,有效载荷} as any ) as RE}}
编译器要我传递第二个类型参数.
const fooCreator =defineEvent<{有效载荷:{富:字符串}//预期有2个类型参数,但得到了1个.}>("foo");
有谁知道如何做到这一点,或者如果这是不可能的?
这里的问题是 TypeScript 还不支持 部分类型参数推断;您的 defineEvent()
函数从根本上取决于两种类型:有效载荷类型(称之为 P
),以及,呃,type"字符串文字类型(称之为 >T
).您想指定 P
但让编译器推断 T
,这是不支持的.您可以同时指定它们,或者编译器可以尝试推断它们.
所以我知道有两种可能的解决方法.一种是使用柯里化,其中一个函数返回另一个函数.第一个函数在 P
中是泛型的,你将指定它,返回的函数在 T
中是泛型的,这将从函数的参数中推断出来.像这样:
const defEvent = () =><K扩展字符串>(类型:K)=>(有效载荷:P) =>({类型,有效载荷});const fooCreator = defEvent<{ foo: string }>()("foo");const barCreator = defEvent<{ bar: string }>()("bar");
这为您提供了与之前相同的 fooCreator
和 barCreator
对象,并且您可以准确指定负载类型和类型字符串.这里的尴尬在于额外的链式函数调用.
另一种解决方法是使用一个虚拟负载参数,它允许编译器推断 T
和 P
.defineEvent()
函数体不使用虚拟参数;它只被类型系统使用.这也意味着您实际上并不需要传入 P
类型的实际值;您可以使用 type assertion 传递类似null
或 undefined
:
const defEvent = (payloadDummy: P, type: T) =>(有效载荷:P) =>({类型,有效载荷});const fooCreator = defEvent(null! as { foo: string }, "foo");const barCreator = defEvent(null! as { bar: string }, "bar");
这又是你所拥有的 fooCreator
和 barCreator
.当然,这里的尴尬在于使用虚拟参数.我更喜欢柯里化而不是虚拟化,但这取决于你.
好的,希望有帮助;祝你好运!
Background
Currently creating events in my TypeScript app looks like this:
// This function creates event creators
function defineEvent<E extends {type:string, payload:any}>(type:E["type"]) {
return (payload:E["payload"]) => ({
type,
payload
})
}
// This function creates foo events
const fooCreator = defineEvent<{
type:"foo",
payload: {
foo:string
}
}>("foo");
// This function creates bar events
const barCreator = defineEvent<{
type:"bar",
payload:{
bar:string
}
}>("bar");
// Example events
const fooEvent = fooCreator({foo:'foo'});
const barEvent = barCreator({bar:'bar'});
// Create a union type to switch over
type AppEvent = ReturnType<typeof fooCreator> | ReturnType<typeof barCreator>;
// Example of switching with a discriminated union
function switchOnEvents(event: AppEvent) {
switch(event.type){
case "foo":
// compiler is happy about payload having a foo property
return event.payload.foo.toLowerCase();
case "bar":
// compiler is happy about payload having a bar property
return event.payload.bar.toLowerCase();
}
}
Question
This works and is okish however this means I need to specify the event type twice when defining event creators which in some ways could be considered redundant.
const fooCreator = defineEvent<{
type:"foo", // defining type here
payload: {
foo:string
}
}>("foo"); // also here to create the actual string value
Is it possible to create a return type for the creator function that extracts the string literal from the input type
argument to the factory function?
Which would mean the example above would still work if the event creators were provided like so:
const barCreator = defineEvent<{
payload:{
bar:string
}
}>("bar");
Failed Attempt
I am guessing I need to merge two types but somehow infer the string literal.
However this doesn't work:
function defineEvent<E extends { payload:any}, ET extends string>(type:ET) {
type RE = E & {type: ET};
return (payload:E["payload"]) => {
return ({
type,
payload
} as any ) as RE
}
}
The compiler wants me to pass a second type argument.
const fooCreator = defineEvent<{
payload: {
foo:string
} //Expected 2 type arguments, but got 1.
}>("foo");
Anyone know how to do this or if it is impossible?
The problem here is that TypeScript does not yet support partial type parameter inference; your defineEvent()
function fundamentally depends on two types: the payload type (call it P
), and the, uh, "type" string literal type (call it T
). You want to specify P
but have the compiler infer T
, and that's not supported. You can either specify both of them, or the compiler can try to infer both of them.
So there are two possible workarounds I know of. One is to use currying, where a function returns another function. The first function is generic in P
, which you will specify, and the returned function is generic in T
, which will be inferred from the function's argument. Like this:
const defEvent = <P>() => <K extends string>(type: K) => (payload: P) => ({
type,
payload
});
const fooCreator = defEvent<{ foo: string }>()("foo");
const barCreator = defEvent<{ bar: string }>()("bar");
This gives you the same fooCreator
and barCreator
objects you had before, and you specify exactly the payload type and the type string. The awkwardness here is in the extra chained function call.
The other workaround is to use a dummy payload parameter which allows the compiler to infer both T
and P
. The dummy parameter is not used by the body of the defineEvent()
function; it's only used by the type system. That also means you don't really need to pass an actual value of type P
in; you can use a type assertion to pass something like null
or undefined
:
const defEvent = <P, T extends string>(payloadDummy: P, type: T) => (
payload: P
) => ({
type,
payload
});
const fooCreator = defEvent(null! as { foo: string }, "foo");
const barCreator = defEvent(null! as { bar: string }, "bar");
This is again, the same fooCreator
and barCreator
you had. The awkwardness here, of course, is using the dummy parameters. I tend to prefer currying over dummying, but that's up to you.
Okay, hope that helps; good luck!
这篇关于在 TypeScript 中,是否可以从字符串的输入类型推断出可区分联合的字符串文字类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!