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。
