Typescript如何创建具有两种常见属性的类型?

时间:2017-11-19 10:42:17

标签: typescript

有两种类型

type A = {
  x: number
  y: number
}

type B = {
  y: number
  z: number
}

如何获取具有该类型的公共属性的类型?

type C = Something<T1, T2> // { y: number }

4 个答案:

答案 0 :(得分:11)

公共属性

使用静态keyof运算符:

type Ka = keyof A // 'x' | 'y'
type Kb = keyof B // 'y' | 'z'
type Kc = Ka & Kb // 'y'

使用Kc中的属性定义Mapped Type

type C = {
  [K in keyof A & keyof B]: A[K] | B[K]
}

这定义了一种新类型,其中每个键都会出现在AB中。

与此密钥相关联的每个值都将具有A[K] | B[K]类型,以防A[K]B[K]不同。

仅具有相同类型的公共属性

仅当在A和B中键入相同时,使用Conditional Type将键映射到值:

type MappedC = {
  [K in keyof A & keyof B]:
    A[K] extends B[K] // Basic check for simplicity here.
    ? K // Value becomes same as key
    : never // Or `never` if check did not pass
}

通过访问所有密钥,从此对象获取所有值的联合

// `never` will not appear in the union
type Kc = MappedC[keyof A & keyof B]

最后:

type C = {
  [K in Kc]: A[K]
}

答案 1 :(得分:2)

仅提取相同类型的普通道具

基于@kube's answer

type Common<A, B> = Pick<
  A,
  {
    [K in keyof A & keyof B]: A[K] extends B[K]
      ? B[K] extends A[K]
        ? K
        : never
      : never;
  }[keyof A & keyof B]
>;

答案 2 :(得分:1)

基于@kube's answer,您可以使用泛型来创建可重用的类型:

type Common<A, B> = {
    [P in keyof A & keyof B]: A[P] | B[P];
}

这允许您动态创建交叉点:

const c: Common<T1, T2> = { y: 123 };

答案 3 :(得分:1)

幼稚方法的缺点

虽然其他答案中通常建议使用以下类型:

type Common<A, B> = {
    [P in keyof A & keyof B]: A[P] | B[P];
}

无法检查属性值是否可以相互分配。 这意味着 Common<A, B> 可能具有以下属性 AB 并未真正共享。

type A = { a: number; x: string; z: number }
type B = { b: number; x: number; z: number}
Common<A, B> // => { x: string | number; z: number}
// above should just be { z: number }, since the type of property x is not
// assignable to the type of property x in both A and B.

这很糟糕的原因是因为解构 Common<A, B> 类型的对象 进入 A 类型或 B 类型的对象可能因 AB 失败。 例如,

const sharedProps: Common<A, B> = { x: 'asdf', z: 9 }
const a: A = { ...sharedProps, a: 1 }

// below throws type error; string not assignable to number
const b: B = { ...sharedProps, b: 1 }

这令人困惑,并且对我们可以用 泛型。

涵盖上述用例的深入方法

我建议使用以下类型(适用于 TS4.1+):

/**
 * Omits properties that have type `never`. Utilizes key-remapping introduced in
 * TS4.1.
 *
 * @example
 * ```ts
 * type A = { x: never; y: string; }
 * OmitNever<A> // => { y: string; }
 * ```
 */
type OmitNever<T extends Record<string, unknown>> = {
  [K in keyof T as T[K] extends never ? never : K]: T[K];
};

/**
 * Constructs a Record type that only includes shared properties between `A` and
 * `B`. If the value of a key is different in `A` and `B`, `SharedProperties<A,
 * B>` attempts to choose a type that is assignable to the types of both values.
 *
 * Note that this is NOT equivalent to `A & B`.
 *
 * @example
 * ```ts
 * type A = { x: string; y: string; }
 * type B = { y: string; z: string }
 * type C = { y: string | number; }
 *
 * A & B                  // => { x: string; y: string; z: string; }
 * SharedProperties<A, B> // => { y: string; }
 * SharedProperties<B, C> // => { y: string | number; }
 * ```
 */
type SharedProperties<A, B> = OmitNever<Pick<A & B, keyof A & keyof B>>;

此类型正确返回保证是 AB 的子类型的共享属性,因为 A & B 保证是 A 和 {{ 1}} 只要 B 不是 A & B

never