网站搜索

javascript - 我如何限制已使用类型中的键?


我写一个库

type State = {
  playing: boolean
  phases: {
    [key: string]: {
      progress: number
    }
  }
}

class BaseWidget {
  state: State

  constructor() {
    this.state = {
      playing: false,
      phases: {},
    }
  }
}

我这样使用它

class Widget extends BaseWidget {
    constructor() {
        super()
        this.state.phases = {
            start: {
                progress: 0,
            },
            finish: {
                progress: 0,
            },
        }
    }
}

不同的继承自 BaseWidget 的类有不同的阶段
我如何指定这个 Widget 类只会有 start 和 finish?
是否有可能从 Widget 类中明确 State 类型?
还是我需要使用泛型?
也许我需要使用不是 [key: string],而是 [key in keyof T]?但是我该如何传递 T?正确的语法是什么?
谢谢!

答案#1

你不能使用索引签名来限制键的类型,但你可以使用 Record 工具类型来得到相同的结果。因为你最初将 phases 初始化为 {},所以你需要将其设为 Partial<Record<...>>:

type State<T extends string> = {
  playing: boolean
  phases: Partial<Record<T, {
    progress: number
  }>>
}

class BaseWidget<T extends string> {
  state: State<T>

  constructor() {
    this.state = {
      playing: false,
      phases: {},
    }
  }
}

type WidgetPhase = 'start' | 'finish'

class Widget extends BaseWidget<WidgetPhase> {
    constructor() {
        super()
        this.state.phases = {
            start: {
                progress: 0,
            },
            finish: {
                progress: 0,
            },
            nonexistent: { // Compile error!
                progress: 0,
            }
        }
    }
}

游乐场链接

如果你实际上不希望它是 Partial,你可以通过基类的构造函数来初始化它;这保证了所有键始终存在:

type Phase = {
    progress: number
}

type State<T extends string> = {
  playing: boolean
  phases: Record<T, Phase>
}

class BaseWidget<T extends string> {
  state: State<T>

  constructor(phases: Record<T, Phase>) {
    this.state = {
      playing: false,
      phases: phases,
    }
  }
}

type WidgetPhase = 'start' | 'finish'

class Widget extends BaseWidget<WidgetPhase> {
    constructor() {
        super({
            start: {
                progress: 0,
            },
            finish: {
                progress: 0,
            },
        })
    }
}

游乐场链接

答案#2

你的问题有点宽泛,但看看你想要实现的目标,我会创建类似这样的东西:

type State = Record<string, any>;

class BaseWidget<S extends State> {
  state: S;

  constructor(initState: S) {
    this.state = initState;
  }
}

interface HasStartPhase {
  start: {
    progress: number;
  };
}

interface HasFinishPhase {
  finish: {
    progress: number;
    extraInfo: string;
  };
}

interface WidgetState extends State {
  playing: boolean;
  phases: HasStartPhase & HasFinishPhase;
}

class Widget extends BaseWidget<WidgetState> {
  constructor() {
    super({
      playing: false,
      phases: {
        start: {
          progress: 0
        },
        finish: {
          progress: 0,
          extraInfo: "abc"
        }
      }
    });
  }
}

它允许您的 BaseWidget 中的任何状态,并且特定的 Widgets 可以指定它们自己的状态形状。为阶段创建接口可以在需要时混合搭配使用它们。

这里有一个供你尝试的 TS Playground。