打字稿:根据对象属性中的属性确定对象类型

时间:2020-07-20 02:26:38

标签: typescript discriminated-union conditional-types

我可能问的Typescript太多了,但我想知道是否可能这样:

interface ObjectType {
    type: 'this' | 'that';
}

interface SomeObject {
    objType: ObjectType
}

interface ThisObject extends SomeObject {
    objType: { type: 'this' }
    thisProp: 'anything'
}

interface ThatObject extends SomeObject {
    objType: { type: 'that' }; 
    thatProp: 'something'
}

function getProp(obj: ThisObject | ThatObject) {
    switch (obj.objType.type) {
        case 'this':
            return {
                type: obj.objType.type,
                prop: obj.thisProp
            };
        case 'that':
            return {
                type: obj.objType.type,
                prop: obj.thatProp
            };
    }
}

Typescript能够正确缩小obj.objType.type的范围,但是我尝试分配给返回对象中的prop的值不会进行类型检查。两者的错误相同(显然,属性名称不同):

TS2339: Property 'thatProp' does not exist on type 'ThisObject | ThatObject'. Property 'thatProp' does not exist on type 'ThisObject'.

像这样可能吗?我也尝试过这样的事情:

interface SomeObject {
    objType: ObjectType;
    thisProp: SomeObject['objType'] extends 'this' ? 'anything' : never;
    thatProp: SomeObject['objType'] extends 'that' ? 'something' : never;
}

这将导致两个道具均为never,以及类似的内容:

type PickObject<T> = T extends 'this' ? ThisObject : ThatObject;

function getProp<T extends 'this' | 'that'>(obj: PickObject<T>) {
    switch (obj.objType.type) {
        case 'this':
            return {
                type: obj.objType.type,
                prop: obj.thisProp
            };
        case 'that':
            return {
                type: obj.objType.type,
                prop: obj.thatProp
            };
    }
}

这将导致相同的错误:

TS2339: Property 'thisProp' does not exist on type 'PickObject '.

3 个答案:

答案 0 :(得分:1)

假设您的类型实际上是这样的:

interface ThisObject extends SomeObject { objType: { type: 'this' }; thisProp: 'anything' }
interface ThatObject extends SomeObject { objType: { type: 'that' }; thatProp: 'something' }

其中type属性实际上嵌套在objType属性中,然后您遇到了microsoft/TypeScript#18758所提出的问题,其中TypeScript并不真正支持嵌套的discriminated unions。您不能将受歧视的联合用作另一个受歧视的联合的判别;判别式必须是像"this" | "that"这样的单例类型的并集。


您可以编写自己的user-defined type guard function而不是等待microsoft / TypeScript#18758得到解决,而是通过传递判别式 object 并使用{ {3}}表示所需的缩小。像这样:

function nestedDiscrim<T extends object | PropertyKey, D extends object | PropertyKey>(
  val: T, discriminant: D): val is Extract<T, D>;
function nestedDiscrim(val: any, discriminant: any) {
  if ((typeof val === "object") && (typeof discriminant === "object")) {
    for (let k in discriminant) {
      if (!(k in val)) return false;
      if (!nestedDiscrim(val[k], discriminant[k])) return false;
    }
    return true;
  }
  if ((typeof val !== "object") && (typeof discriminant !== "object")) {
    return val === discriminant;
  }
  return false;
}

您将像这样使用它:

function getProp(obj: ThisObject | ThatObject) {
  if (nestedDiscrim(obj, { objType: { type: "this" } } as const)) {
    return {
      type: obj.objType.type,
      prop: obj.thisProp
    };
  } else {
    return {
      type: obj.objType.type,
      prop: obj.thatProp
    };
  }
}

您可以看到它有效:

console.log(getProp({ objType: { type: "this" }, thisProp: "anything" })); // {type: "this", prop: "anything"};
console.log(getProp({ objType: { type: "that" }, thatProp: "something" })); // {type: "that", prop: "something"};

这个想法是nestedDiscrim(obj, { objType: { type: "this" } })走过obj,看着obj.objType.type并将其与"this"进行比较。如果它们相同,则将objThisObject | ThatObject缩小到ThisObject。否则,我们将obj的范围从ThisObject | ThatObject缩小到ThatObject


好的,希望能有所帮助;祝你好运!

the Extract utility type

答案 1 :(得分:0)

似乎不需要SomeObject界面。这是一种消除了SomeObject接口的可能解决方案:

TypeScript playground demo

interface ObjectType {
    type: 'this' | 'that';
}

interface ThisObject extends ObjectType {
    type: 'this'
    thisProp: 'anything'
}

interface ThatObject extends ObjectType {
    type: 'that'
    thatProp: 'something'
}

function getProp(obj: ThisObject | ThatObject) {
    switch (obj.type) {
        case 'this':
            return {
                type: obj.type,
                prop: obj.thisProp
            };
        case 'that':
            return {
                type: obj.type,
                prop: obj.thatProp
            };
    }
}

答案 2 :(得分:0)

问题是ObjectTypeobjType键。 objType实际上并未定义接口的类型。这是一个工作示例:

type ObjectType = 'this' | 'that'

interface SomeObject {
  type: ObjectType
}

interface ThisObject extends SomeObject {
  type: 'this'
  thisProp: 'anything'
}

interface ThatObject extends SomeObject {
  type: 'that'
  thatProp: 'something'
}

function getProp(obj: ThisObject | ThatObject) {
  switch (obj.type) {
    case 'this':
      return {
        type: obj.type,
        prop: obj.thisProp
      };
    case 'that':
      return {
        type: obj.type,
        prop: obj.thatProp
      };
  }
}