打字稿只有一种或另一种

时间:2019-12-24 23:06:29

标签: typescript

我想返回一个对象作为获取响应,该对象可以具有以下属性之一(数据或消息):

{ data: Data } | { mes: ErrMessage }

问题是打字稿抱怨这个对象,比如说道具:

if (prop.mes) return // Property 'mes' does not exist on type '{ data: string; }'.
prop.data // Property 'data' does not exist on type '{ mes: string; }'.

除了(每次在组件中定义这种大类型并不方便)之外,是否还有其他选择:

{ data: Data, mes: false } | { mes: ErrMessage, data: false}

打字机游乐场example

type ErrMessage = string // some complex structure

const fetchAPI = <Body = any, Data = any>(link: string, body: Body): Promise<{ data: Data } | { mes: ErrMessage }> => {
  return new Promise((resolve) => {
      fetch(link, { body: JSON.stringify(body) })
      .then((raw) => raw.json())
      .then((res) => {
        resolve(res.err ? { mes: res.err } : { data: res })
      })
      .catch((err) => {
        resolve({ mes: 'text' })
      })
  })
}

type Data = string // some complex structure
type MyReq = string // some complex structure
type MyRes = {data: Data } | {mes: ErrMessage}

const someLoader = async () => {
    const res = await fetchAPI<MyReq, MyRes>('reader', 'body')
    return {res}
}

const componentThatGetProp = (prop: MyRes ) => {
    // error handler
    if (prop.mes) return // Property 'mes' does not exist on type '{ data: string; }'.

    // do something
    prop.data // Property 'data' does not exist on type '{ mes: string; }'. 
}

2 个答案:

答案 0 :(得分:4)

代替

if (prop.mes) return

尝试

if ('mes' in prop) return

一个尝试访问访问该属性(可能不存在),另一个检查其是否存在

答案 1 :(得分:2)

供TypeScript使用的“正确”类型是

type MyRes = { data: Data, mes?: undefined} | { mes: ErrMessage, data?: undefined}

其中,联合的每个成员中无关的属性都是可选的,并且具有undefined值,而不是false值。 (或者您可以使用never而不是undefined,因为可选属性始终会添加| undefined,而never | undefined只是undefined。)这对应于如果您检查不相关的属性,实际会发生什么:它将丢失。 As of TypeScript 3.2,编译器将上述类型视为discriminated union,您的错误将消失:

const componentThatGetProp = (prop: MyRes) => {
  // error handler
  if (prop.mes) return // okay
  // do something
  prop.data // okay
}

我同意,将现有的MyRes的联合类型手动转换为每个联合成员都包含来自所有其他联合成员的所有键的痛苦。幸运的是,您不需要手动执行此操作。使用TypeScript的conditional types,您可以通过编程方式将“常规”联合处理为上述区分形式。这是一种实现方法,使用名为“ ExclusifyUnion<T>”的类型别名来表示它采用联合类型T,并将其转换为新的联合类型,编译器可以在其中声明任何值该类型必须与联盟的一个成员完全匹配

type AllKeys<T> = T extends any ? keyof T : never;

type ExclusifyUnion<T, K extends AllKeys<T> = AllKeys<T>> =
  T extends any ?
  (T & { [P in Exclude<K, keyof T>]?: never }) extends infer U ?
  { [Q in keyof U]: U[Q] } :
  never : never;

如果可以的话,我可以解释一下,但是草图是这样的:AllKeys<T>从联合T返回所有键,所以AllKeys<{a: string} | {b: number}>"a" | "b"ExclusifyUnion<T>将并集的每个成员相交,并将其与包含所有其余键的类型相交,作为类型为never的可选属性。因此ExclusifyUnion<{a: string} | {b: number} | {c: boolean}>最终看起来像{a: string, b?: never, c?: never} | {b: number, a?: never, c?: never} | {c: boolean, a?: never, b?: never}。这是MyRes的作用:

type MyRes = ExclusifyUnion<{ data: Data } | { mes: ErrMessage }>
/*type MyRes = {
    data: string;
    mes?: undefined;
} | {
    mes: string;
    data?: undefined;
}*/

即使MyRes拥有更多具有更多属性的联合成员,这也应该易于扩展。


好的,希望能给您前进的道路。祝你好运!

Playground link to code