英文:
Typescript: union of interface properties names when interface inherits
问题
以下是您提供的代码的翻译部分:
export interface SingleAction {
//...
}
export interface Actions {
[key: string]: SingleAction
}
//
export interface ExampleActions extends Actions {
testAction: {
//...
}
testAction2: {
//...
}
}
抽象类型 Actions
仅用于保护真实 ActionList 类型的形状(例如 ExampleActions
)
现在我们需要一个类型,它是 ExampleActions
中所有属性名称的联合:
// 期望 Test 为 "testAction" | "testAction2" 但实际上是字符串
type Test = keyof ExampleActions;
const a: Test = "gfhgfh"; // 有效,但应该仅需要 "testAction" | "testAction2"
当我在 export interface ExampleActions extends Actions
行中去掉 extends Actions
时,它按预期工作。
如何在不去掉它的情况下实现这一点?
为什么我需要它?
我想让某个类知道某个方法调用中允许的参数,就像这样:
class ActionsManager<CALLABLE_ACTIONS extends Actions> {
callAction(name: keyof CALLABLE_ACTIONS) {
//...
}
}
const am = new ActionsManager<ExampleActions>();
am.callAction("testAction"); // 正确
am.callAction("testAction2"); // 正确
am.callAction("anotherAction"); // 错误,"ExampleActions" 接口中没有属性 "anotherAction"
可能相关的链接:
https://github.com/microsoft/TypeScript/issues/24274
https://github.com/DanielXMoore/Civet/issues/271
看起来现在可能不可能?但有关键字 satisfies
,一些人考虑将其引入到 TypeScript 语法中。
英文:
We have that code:
export interface SingleAction {
//...
}
export interface Actions {
[key: string]: SingleAction
}
//
export interface ExampleActions extends Actions {
testAction: {
//...
}
testAction2: {
//...
}
}
Abstract type Actions
is just for guarding shapes of real ActionList like types (for example ExampleActions
)
Now we need type that is union of all properties names in ExampleActions:
// expecting Test to be "testAction" | "testAction2" but instead it's string
type Test = keyof ExampleActions;
const a: Test = "gfhgfh"; // valid, but should require only "testAction" | "testAction2"
When I drop extends Actions
in line export interface ExampleActions extends Actions {
then it works as expected.
How to make it happen without droping it?
Why I need it?
I want to some class knows allowed parameteres in some method call, like this:
class ActionsManager<CALLABLE_ACTIONS extends Actions> {
callAction(name: keyof CALLABLE_ACTIONS) {
//...
}
}
const am = new ActionsManager<ExampleActions>();
am.callAction("testAction"); //good
am.callAction("testAction2"); //good
am.callAction("anotherAction"); //wrong, there is no property "anotherAction" in "ExampleActions" interface
May be related:
https://github.com/microsoft/TypeScript/issues/24274
https://github.com/DanielXMoore/Civet/issues/271
looks like it isn't possible right now? But there is keyword satisfies
that some people consider to introduce to Typescript syntax.
答案1
得分: 1
Ideally the time to pull this information out of ExampleActions
is before you mix it with Actions
via interface extension and before these known keys are absorbed into string
:
interface BaseExampleActions {
testAction: {}
testAction2: {}
}
interface ExampleActions extends Actions, BaseExampleActions { }
type Test = keyof BaseExampleActions;
const a: Test = "gfhgfh"; // error!
But if you already have ExampleActions
defined that way and really need to extract the known keys and ignore the index signature, then you can use key remapping in mapped types to do it:
type KnownKeys<T> = keyof {
[K in keyof T as {} extends Record<K, any> ? never : K]: any
}
Here what we're doing is checking each key K
in keyof T
to see if it's one of the valid index signature key types like string
, number
, symbol
or a pattern template literal type such as `abc${string}def`
. The only way I know of to do that is to test for whether an object with that key type is allowed to be empty. A literal type like "a"
fails that test, because {}
cannot be assigned to {a: any}
. But a type like string
passes, because {}
can be assigned to {[k: string]: any}
.
Anyway, let's test it out:
type Test = KnownKeys<ExampleActions>;
// type Test = "testAction" | "testAction2"
Looks good. KnownKeys<ExampleActions>
returns just "testAction" | "testAction2"
and the string
part is discarded.
英文:
Ideally the time to pull this information out of ExampleActions
is before you mix it with Actions
via interface extension and before these known keys are absorbed into string
:
interface BaseExampleActions {
testAction: {}
testAction2: {}
}
interface ExampleActions extends Actions, BaseExampleActions { }
type Test = keyof BaseExampleActions;
const a: Test = "gfhgfh"; // error!
But if you already have ExampleActions
defined that way and really need to extract the known keys and ignore the index signature, then you can use key remapping in mapped types to do it:
type KnownKeys<T> = keyof {
[K in keyof T as {} extends Record<K, any> ? never : K]: any
}
Here what we're doing is checking each key K
in keyof T
to see if it's one of the valid index signature key types like string
, number
, symbol
or a pattern template literal type such as `abc${string}def`
. The only way I know of to do that is to test for whether an object with that key type is allowed to be empty. A literal type like "a"
fails that test, because {}
cannot be assigned to {a: any}
. But a type like string
passes, because {}
can be assigned to {[k: string]: any}
.
Anyway, let's test it out:
type Test = KnownKeys<ExampleActions>;
// type Test = "testAction" | "testAction2"
Looks good. KnownKeys<ExampleActions>
returns just "testAction" | "testAction2"
and the string
part is discarded.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论