Typescript 从判别联合数组中查找的窄返回类型

Typescript 从判别联合数组中查找的窄返回类型,typescript,type-conversion,discriminated-union,Typescript,Type Conversion,Discriminated Union,我经常使用下面示例中的代码,我想知道是否有某种智能方法可以键入查找结果,而不必执行显式类型断言 type Foo = { type: "Foo" }; type Goo = { type: "Goo" }; type Union = Foo | Goo; const arr: Union[] = []; const foo = arr.find(a => a.type === "Foo") as Foo; 如果省略了as Foo类型断言,则结果是类型Union,即使它只能返回类型Foo

我经常使用下面示例中的代码,我想知道是否有某种智能方法可以键入
查找
结果,而不必执行显式类型断言

type Foo = { type: "Foo" };
type Goo = { type: "Goo" };
type Union = Foo | Goo;

const arr: Union[] = [];
const foo = arr.find(a => a.type === "Foo") as Foo;
如果省略了
as Foo
类型断言,则结果是类型
Union
,即使它只能返回类型
Foo

在下面的示例中,修复
find
类型以返回缩小的类型的最干净方法是什么

编辑:此问题也适用于
过滤器
和其他类似方法

Edit2:建议的类似问题的公认答案()表明,通过在
find/filter
的谓词中使用类型guard,可以缩小返回值的范围


如果区分字符串文字总是在
type
键下,那么这个类型保护函数应该如何缩小任何区分的并集呢?

如果您想要一个用于用户定义类型保护函数的生成器,该函数返回一个区分区分的并集的类型谓词,它可能看起来像这样:

function discriminate<K extends PropertyKey, V extends string | number | boolean>(
    discriminantKey: K, discriminantValue: V
) {
    return <T extends Record<K, any>>(
        obj: T & Record<K, V extends T[K] ? T[K] : V>
    ): obj is Extract<T, Record<K, V>> =>
        obj[discriminantKey] === discriminantValue;
}
看起来不错。下面是传递不适用的字段/值时发生的情况:

const mistake1 = arr.find(discriminate("hype", "Foo")); // error!
// ------------------->   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Union is not assignable to Record<"hype", any>.
const mistake2 = arr.find(discriminate("type", "Hoo")); // error!
// ------------------->   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Union is not assignable to ((Foo | Goo) & Record<"type", "Hoo">)
const mistake1=arr.find(辨别(“炒作”、“Foo”);//错误!
// ------------------->   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//工会是不可转让的记录。
const mistake2=arr.find(辨别(“类型”、“Hoo”);//错误!
// ------------------->   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
//工会不可转让给((Foo | Goo)和Record)
好吧,希望这会有帮助;祝你好运


这是上面答案中的jcalz代码,用否定和并集进行了扩展

export function isDiscriminate<K extends PropertyKey, V extends string | number | boolean>(
    discriminantKey: K, discriminantValue: V | V[]
) {
    return <T extends Record<K, any>>(
        obj: T & Record<K, V extends T[K] ? T[K] : V>
    ): obj is Extract<T, Record<K, V>> =>
        Array.isArray(discriminantValue) 
            ? discriminantValue.some(v => obj[discriminantKey] === v)
            : obj[discriminantKey] === discriminantValue;
}

export function isNotDiscriminate<K extends PropertyKey, V extends string | number | boolean>(
    discriminantKey: K, discriminantValue: V | V[]
) {
    return <T extends Record<K, any>>(
        obj: T & Record<K, V extends T[K] ? T[K] : V>
    ): obj is Exclude<T, Record<K, V>> =>
        Array.isArray(discriminantValue)
            ? discriminantValue.some(v => obj[discriminantKey] === v)
            : obj[discriminantKey] === discriminantValue;
}

公认的答案很好,但很难理解。我是这样做的。它的可重用性较差,只处理一种类型,但更易于阅读

function isType<V extends Union['type']>(val: V) {
  return (obj: Union):
      obj is Extract<Union, {type: V}> => obj.type === val;
}

const found = arr.find(isType('Foo'));
函数isType(val:V){
返回(对象:联合):
obj是Extract=>obj.type==val;
}
const found=arr.find(isType('Foo'));
感谢jcalz的支持

我为谓词卫士注释了一个稍微不同的工厂风格:

function hasProp<K extends PropertyKey, V extends string | number | boolean>(k: K, v: V) {
  
  // All candidate types might have key `K` of any type
  type Candidate = Partial<Record<K, any>> | null | undefined

  // All matching subtypes of T must have key `K` equal value `V`
  type Match<T extends Candidate> = Extract<T, Record<K, V>>

  return <T extends Candidate>(obj: T): obj is Match<T> => (
    obj?.[k] === v
  )
}
函数hasProp(k:k,v:v){
//所有候选类型都可能具有任意类型的键'K'
候选类型=部分|空|未定义
//T的所有匹配子类型的键'K'必须等于'V`
类型匹配=提取
返回(obj:T):obj是匹配=>(
obj?[k]==v
)
}

这是否回答了您的问题?不完全是这样,但有帮助。现在,我想知道如何编写一些泛型类型保护函数来缩小这样的有区别的联合,这样我就能够编写
arr.find(isTypeFromUnion(“Foo”))
。您可以将
find
的回调函数定义为如下类型保护:
const maybeFoo=arr.find((a):a是Foo=>a.type==“Foo”); // Foo |未定义
。这只是将
作为Foo
移动到
a is Foo
,这意味着我仍然需要显式地编写typeAfaik。目前编译器无法自动推断类型保护并缩小联合,请参见和。是的,这很有效。正是我所期望的。你能把它扩展到否定和联合吗<代码>a.type!==“Foo”和
a.type==“Foo”| a.type==“Goo”
很可能是。求反将使用
Exclude
而不是
Extract
,对于union,您需要接受类型为
V[]
的数组/rest参数,并检查数组中的每个元素。
function isType<V extends Union['type']>(val: V) {
  return (obj: Union):
      obj is Extract<Union, {type: V}> => obj.type === val;
}

const found = arr.find(isType('Foo'));
function hasProp<K extends PropertyKey, V extends string | number | boolean>(k: K, v: V) {
  
  // All candidate types might have key `K` of any type
  type Candidate = Partial<Record<K, any>> | null | undefined

  // All matching subtypes of T must have key `K` equal value `V`
  type Match<T extends Candidate> = Extract<T, Record<K, V>>

  return <T extends Candidate>(obj: T): obj is Match<T> => (
    obj?.[k] === v
  )
}