【问题标题】:Create extendable enums for use in extendable interfaces创建用于可扩展接口的可扩展枚举
【发布时间】:2017-09-03 16:24:42
【问题描述】:

上下文:我正在尝试使用TypeState 库开发一种在打字稿中创建可扩展状态机的模式。 TypeState 为 Typescript 提供了一个类型安全的状态机,虽然不是我遇到的问题的核心,但它有助于说明我的目标。

问题:我在创建可扩展模式以在 Typescript 中扩展 enum 并在 interfaceclass 声明中实现它们时遇到问题。

目标:下面的伪代码说明了我希望我的模式是什么样的。

1) 定义基址enum States

2) 扩展 enum States 与其他状态导致 enum ExtendedStates

2) 使用States 和类型化状态机定义ParentInterface

3) 通过ChildInterface 扩展ParentInterface 并用ExtendedStates 覆盖States

4) 在class Parent 中实现ParentInterface

5) 在class Child 中扩展class Parent 实现ChildInterface

6) 能够从任一类调用broadcastState() 并获取当前状态。

我已经在其他语言中使用这种模式取得了很好的效果,如果能帮助我理解 Typescript 的局限性以及可以实现相同目标的任何替代模式,我将不胜感激。

import {TypeState} from "typestate";

enum States {
  InitialState
}

// extends is not available on enum, looking for alternative
enum ExtendedStates extends States {
  AdditionalState
}

/////////////////////////////////////////
// this works fine
interface ParentInterface {
  fsm: TypeState.FiniteStateMachine<States>;
  states: typeof States;
  message: string;
}

// incorrectly extends ParentInterface, types of fsm/states are incompatible
interface ChildInterface extends ParentInterface {
  fsm: TypeState.FiniteStateMachine<ExtendedStates>;
  states: typeof ExtendedStates;
}

/////////////////////////////////////////

class Parent implements ParentInterface {
  public fsm: TypeState.FiniteStateMachine<States>;
  public states: typeof States;
  public message: string = "The current state is: ";

  constructor(state: States | undefined) {
    state = state ? state : this.states.InitialState;
    this.fsm = new TypeState.FiniteStateMachine(state);
    this.broadcastCurrentState();
  }

  public broadcastCurrentState(): void {
    console.log(this.message + this.fsm.currentState);
  }
}

class Child extends Parent implements ChildInterface {
  public fsm: TypeState.FiniteStateMachine<ExtendedStates>;
  public states: typeof ExtendedStates;

  constructor(state: ExtendedStates | undefined) {
    state = state ? state : this.states.InitialState;
    this.fsm = new TypeState.FiniteStateMachine(ExtendedStates);
    this.broadcastCurrentState();
  }
}

最近的一次

import {TypeState} from "typestate";

enum States {
  InitialState
}

enum ExtendedStates {
  InitialState,
  ExtendedState
}

class Parent {
  public fsm: TypeState.FiniteStateMachine<States>;
  public states: typeof States;
  public message: string = "The current state is: ";

  // T is declared but never used
  constructor(state: <T> | undefined) {
    state = state ? state : this.states.InitialState;
    // cannot find name T
    this.fsm = new TypeState.FiniteStateMachine<T>(state);
    this.broadcastCurrentState();
  }

  public broadcastCurrentState(): void {
    console.log(this.message + this.fsm.currentState);
  }
}

// types of fsm are incompatible
class Child extends Parent {
  public fsm: TypeState.FiniteStateMachine<ExtendedStates>;
  public states: typeof ExtendedStates;

  constructor(state: ExtendedStates | undefined) {
    // Param not assignable to type <T>
    super(state);
  }
}

此尝试接近预期结果,但无法编译并导致enum 中出现大量代码重复。它还丢失了接口,这些接口不是必需的,但确实提供了一个很好的安全网。

我很想听听大家的意见。我觉得这是一个强大的模式,我缺少一些简单的东西来实现它。

【问题讨论】:

    标签: typescript enums typescript-typings


    【解决方案1】:

    它无法编译的一个原因是Child 不是Parent 的正确子类型。 Liskov substitution principle 表示您应该能够将Child 对象用作Parent 对象。如果我问一个Parent 对象,它的状态机处于哪个状态,它告诉我ExtendedState,那么我就有一个损坏的Parent,对吧?所以Child 是一个损坏的Parent,这很糟糕,这也是 TypeScript 警告你的地方。

    也许最好忘记拥有超类/子类关系而只拥有一个通用类:

    class Generic<T extends States> {
      public fsm: TypeState.FiniteStateMachine<T>;
      public states: T;
      public message: string = "The current state is: ";
    
      // T[keyof T] means the values of T, in this case InitialState, etc    
      constructor(state: T[keyof T] | undefined) {
        state = state ? state : this.states.InitialState;
        // cannot find name T
        this.fsm = new TypeState.FiniteStateMachine<T>(state);
        this.broadcastCurrentState();
      }
    
      public broadcastCurrentState(): void {
        console.log(this.message + this.fsm.currentState);
      }
    }
    

    现在如果States 是正确的对象,工作,但正如您所注意到的,enums 并没有真正的功能齐全,无法以这种方式使用: 你不能得到任何东西来扩展它们。因此,与其使用enum,不如使用模拟它的对象:

    // make our own enum
    type Enum<T extends string> = {[K in T]: K};
    
    // create an enum from given values
    function makeEnum<T extends string>(...vals: T[]): Enum<T> {
      const ret = {} as Enum<T>;
      vals.forEach(k => ret[k] = k)
      return ret;
    }
    
    // take an existing enum and extend it with more values
    function extendEnum<T extends string, U extends string>(
      firstEnum: Enum<T>, ...vals: U[]): Enum<T | U> {
        return Object.assign(makeEnum(...vals), firstEnum) as any;  
    }
    

    在这种情况下,Enum&lt;&gt; 是具有指定字符串键的对象,其值与键相同(这与值是数字的常规 enums 有点不同。如果你真的想要数字可能可以安排,但实现起来会更烦人。我从未使用过TypeState 库,所以我不知道它是否关心值是数字还是字符串。)现在你可以创建你的StatesExtendedStates 像这样:

    const States = makeEnum('InitialState'); 
    type States = typeof States; 
    // States is { InitialState: 'InitialState' };
    
    const ExtendedStates = extendEnum(States, 'ExtendedState');
    type ExtendedStates = typeof ExtendedStates;
    // ExtendedStates is { InitialState: 'InitialState', ExtendedState: 'ExtendedState' };
    

    并像这样创建对象:

    const parentThing = new Generic<States>(States.InitialState);
    const childThing = new Generic<ExtendedStates>(ExtendedStates.InitialState);
    

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

    【讨论】:

      猜你喜欢
      • 2013-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多