TypeScript声明合并 - 更改属性类型

时间:2017-04-13 18:42:53

标签: typescript types typescript-typings

是否可以更改接口中声明的属性的类型?我不知道如何用文字解释这个,所以这里有一个简单的例子,说明我尝试做的事情并不起作用:

假设Movie名称空间是在我无法控制的第三方库中定义的:

declare namespace Movie {
  interface Character {
    doSomething(): Promise<void>;
    friend: Character;
  }
}

现在在我的应用程序中,我希望我的角色成为Super,与超级朋友一起。所以我试着用自己的打字做到这一点:

declare namespace Movie {
  interface Character {
    doAnExtraThing(): Promise<void>; // OK to add a new method
    nickname: string;                // OK to add a new property
    friend: SuperCharacter;          // NOT OK to override type with a subtype
  }

  interface SuperCharacter extends Character {
    doSomethingSuper(): Promise<void>;
  }
}

但这并不起作用,因为TypeScript不允许我用friend覆盖SuperCharacter属性类型,即使定义为SuperCharacter a Character。 TypeScript抱怨此错误:

[ts] Subsequent variable declarations must have the same type. Variable
'friend' must be of type 'Character', but here has type 'SuperCharacter'.

我希望通过SuperCharacter扩展原始的Character界面,我不会遇到这个问题,但我确实如此。

希望能清楚我想要实现的目标。有办法吗?

3 个答案:

答案 0 :(得分:0)

由于friend已经是SuperCharacter,因此没有理由明确指出SuperCharacter可以是Character

所以你可以完全删除这个块:

界面角色{     朋友:SuperCharacter;   }

作为证明,我们可以远离接口,查看一个可以正确编译的类的实现:

class Character {
  friend?: Character

  constructor(friend?: Character) {
    this.friend = friend
  }

  doSomething() {}
}

class SuperCharacter extends Character {
  doSomethingSuper() {}
}


new Character(new Character())
new Character(new SuperCharacter())
new SuperCharacter(new Character())
new SuperCharacter(new SuperCharacter())

答案 1 :(得分:0)

您可以使用ts-toolbelt来实现。但是,您将没有type

import {O} from 'ts-toolbelt'

interface Character {
    doSomething(): Promise<void>
    friend: Character
  }

interface SuperCharacter {
    doSomethingSuper(): Promise<void>;
    friend: SuperCharacter;
}

// SuperCharacter will override Character  
type SuperMerge = O.Merge<SuperCharacter, Character>

// type SuperMerge = {
//     doSomethingSuper: () => Promise<void>;
//     friend: SuperCharacter;
//     doSomething: () => Promise<void>;
// }

然后做

interface SuperMergedCharacter extends SuperMerge {
 ...
}

答案 2 :(得分:0)

我想出了两种解决方案:

解决方案1 ​​

您也可以使用联合类型和条件类型来完成pirix-gh的答案。

首先,我们将两种类型合并在一起:

type Merge<T, U> = T & U

然后我们可以使用条件类型来更改字段好友:

type ChangeProperty<T, K extends keyof T, U> = {
    [Prop in keyof T]: Prop extends K ? U : T[Prop]
}

要产生所需的类型,请像这样使用它:

// From the namespace
interface Character {
    doSomething(): Promise<void>;
    friend: Character;
}

interface SuperCharacter extends Character {
    doSomethingSuper(): Promise<void>;
}

type MyCharacter =  ChangeProperty<Merge<SuperCharacter, SuperCharacter>, "friend", SuperCharacter>

使用这种方法,您不必向SuperCharacter添加字段,您可以重用friend字段并将其类型更改为SuperCharacter

解决方案2

这是基于pirix-gh的答案,但没有库。还使用联合和条件类型。

type SuperMerge<T, U> = {
    [K in keyof (T & U)]: K extends keyof T ? T[K] : K extends keyof U ? U[K] : never
}

类型SuperMerge组合T和U,然后对其进行迭代,然后在T中使用该属性时,它将在T中使用该类型,否则将在U中使用该类型。永不发生。

您可以像这样使用它:

interface SuperCharacter extends Character {
    doSomethingSuper(): Promise<void>;
    friend: SuperCharacter // You need to this line in order to overwrite the default
}

使用此解决方案,您需要将具有新类型的外地朋友添加到界面中,以覆盖默认值。第一种类型包含所有前导属性,并将保留,因此如果第二种类型中有任何重复项,则第一种类型的信息将被覆盖。第二种类型的唯一功能是使用额外的属性扩展第一种类型。

type MyCharacter = SuperMerge<SuperCharacter, Character>

结论

两个解决方案都输出相同的结果:

type MyCharacter = {
    doSomethingSuper: () => Promise<void>;
    doSomething: () => Promise<void>;
    friend: SuperCharacter;
}

为了选择一个,我认为两者都有优点和缺点。当您要合并两种您无法控制的类型时,第一种解决方案很有用,但是您一次只能更改一种类型。您可以递归调用use ChangeType来更改更多属性。

使用解决方案2时,您可以将新属性设置为新类型,并且具有相同名称的所有属性将被第一种类型所取代。

使用两种解决方案,您都可以合并自己的类型。

相关问题