Checking for union type

独自空忆成欢 提交于 2020-03-03 09:11:24

问题


I am looking for a way to have union types as function arguments, then be able to use the arguments, any missing arguments would be undefined

However here, name and age are causing a type issue.

function example(props: { id: number } & ({ name: string } | { age: number })) { 
  const { id, name, age } = props
}

This is what I'd like:

example({ id: 1, name: "Tom" })

example({ id: 1, age: 31 })

回答1:


A small variation of StrictUnion found here will work well:

type UnionKeys<T> = T extends T? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends T? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, undefined>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>

function example(props: StrictUnion<{ id: number } & ({ name: string } | { age: number })>) { 
  const { id, name, age } = props
}

Playground Link

The way StrictUnion works is by ensuring all constituents of the union have all members from all the constituents of the union. It ensures this by adding any members that are missing with the type undefined. So this type: { id: number } & ({ name: string } | { age: number }) will become this type: { id: number; name: string; age: undefined } | { id: number; name: undefined; age: number }. Since this new type has the same structure we can de-structure it.

To build StrictUnion we must first get a union of all the keys from all union constituents. To do this we must use the distributive behavior of conditional types. Using this behavior we can build a type that extracts extracts the keys of each union constituent and creates a union of all. To trigger the distributive behavior we can use an always true condition ( T extends T, T extends unknown or, less ideal T extends any). With this we arrive at the following type extract all the keys:

type UnionKeys<T> = T extends T ? keyof T : never;

Below we can see how this type is applied:

type A = { id: number; name: string }
type B = { id: number; age: number }

UnionKeys<A | B>
  // Conditional type is applied to A and B and the result unioned
  <=> (A extends unknown ? keyof A: never) | (B extends unknown ? keyof B: never) 
  <=> keyof A | keyof B
  <=> ("id" | "name") | ("id" | "age")
  <=> "id" | "name" | "age"

After we have UnionKeys we can use another distributive conditional type to go through each of the union members and see what keys are missing from a given type T (using Exclude<UnionKeys<TAll>, keyof T>) and intersecting the original T with a Partial Record that contains these keys typed as undefined. We need to pass the union to the distributive type twice, once to distribute over (T), and once to have the whole union to be able to extract the keys using UnionKeys.

Below we ca see how this type is applied:

type A = { id: number; name: string }
type B = { id: number; age: number }
StrictUnion<A | B>
  <=> StrictUnionHelper <A | B, A | B>
  // Distributes over T
  <=> (A extends A ? A & Partial<Record<Exclude<UnionKeys<A | B>, keyof A>, undefined>> : never) | (B extends B ? B & Partial<Record<Exclude<UnionKeys<A | B>, keyof B>, undefined>> : never)
  <=> (A extends A ? A & Partial<Record<Exclude<"id" | "name" | "age", "id" | "name">, undefined>> : never) | (B extends B ? B & Partial<Record<Exclude<"id" | "name" | "age", "id" | "age">, undefined>> : never)
  <=> (A extends A ? A & Partial<Record<"age", undefined>> : never) | (B extends B ? B & Partial < Record < "name" >, undefined >> : never)
  // The condition A extends A and B extends B are true and thus the conditional type can be decided
  <=> (A & Partial<Record<"age", undefined>>) | (B & Partial<Record<"name">, undefined>>)
  <=> { id: number; name: string; age?: undefined } | { id: number; age: number; name?: undefined }



回答2:


One possible solution is to default the "undefinable" arguments.

function example(props: { id: number } & ({ name: string } | { age: number })) { 
  const available = { ...{ name: undefined, age: undefined }, ...props }

}


来源:https://stackoverflow.com/questions/59601795/checking-for-union-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!