TypeScript 是不是可以从动态对象中推断出键?
Posted
技术标签:
【中文标题】TypeScript 是不是可以从动态对象中推断出键?【英文标题】:Is it possible for TypeScript to infer keys from a dynamic object?TypeScript 是否可以从动态对象中推断出键? 【发布时间】:2019-03-31 02:45:17 【问题描述】:我在这里尝试实现的是智能感知/自动完成,用于从数组生成的对象 - 类似于 Redux 的 Action Creator,字符串数组 (string[]
) 可以简化为具有形状 [string]: string
。
例如:
const a = ['ONE', 'TWO', 'THREE'];
const b = a.reduce((acc, type) => ( ...acc, [type]: type ), );
console.log(b);
// Output: b = ONE: 'ONE', TWO: 'TWO', THREE: 'THREE' ;
我已经设法让 TypeScript 理解了这么多,使用以下内容。 TypeScript 知道键是字符串,但不知道它们是什么。
interface ITypesReturnObject [s: string]: string
有没有人想出办法通知 TypeScript 对象上的键等于数组中的字符串?
任何帮助将不胜感激。
【问题讨论】:
sn-p 中的注释是预期输出还是错误输出?您能否详细说明您拥有什么以及预期的输出是什么? javascript to TypeScript: Intellisense and dynamic members的可能重复 @CristianS。评论是当前输出,这很好。问题在于调用该对象 - 当我输入b.
时,我没有得到智能感知/自动完成中的键列表,因此该对象的用户无法知道哪些选项可用。
@softbear 谢谢你。它看起来与我试图实现的问题类似,但我不确定我是否知道如何使解决方案在这种情况下工作。如果你这样做,那将不胜感激。
啊哈!有趣的。 IntelliSense 可与new DynamicObject(id:1).id
一起使用,但不适用于您所需要的d = id:1;new DynamicObject(d)
。嗯……肯定是挠头。
【参考方案1】:
(假设你在下面使用TS3.0或更高版本)
TypeScript 支持 string literal types 和 tuple types 的概念,因此可以让您的值 a
具有 type ['ONE', 'TWO', 'THREE']
以及 值 ['ONE', 'TWO', 'THREE']
,像这样:
const a: ['ONE', 'TWO', 'THREE'] = ['ONE', 'TWO', 'THREE'];
(稍后会出现一种不太冗余的方法):
然后您可以使用mapped type 将b
的预期类型表示为从键到与键完全匹配的值的映射:
type SameValueAsKeys<KS extends string[]> = [K in KS[number]]: K ;
可以这样使用:
const b: SameValueAsKeys<typeof a> = a.reduce((acc, type) => ( ...acc, [type]: type ), as any);
b.ONE; // property type is "ONE"
b.TWO; // property type is "TWO"
b.THREE; // property type is "THREE"
注意编译器如何知道b
有三个键,"ONE"
、"TWO"
和"THREE"
,并且值与键相同。
所以 TypeScript 肯定支持这种类型的动态类型。不幸的是,正如我在上面展示的那样,使用起来有点乏味。减少这种烦人的一种方法是添加一些帮助函数,允许编译器推断正确的类型,或者至少将类型断言隐藏在开发人员不必担心的库中。
首先,对于a
...,编译器不会推断元组类型,而且它还倾向于将字符串文字扩展为string
类型,certain instances 除外。让我们引入一个名为stringTuple()
的辅助函数:
function stringTuple<T extends string[]>(...args: T) return args;
这推断出tuple type from rest arguments。现在我们可以使用它来生成a
,而无需输入多余的字符串值:
const a = stringTuple("ONE", "TWO", "THREE");
接下来,让我们介绍一个函数,它接受一个字符串列表并返回一个对象,其键是这些字符串,其值与字符串匹配:
function keyArrayToSameValueAsKeys<T extends string[]>(keys: T): SameValueAsKeys<T>;
function keyArrayToSameValueAsKeys(keys: string[]): [k: string]: string
return keys.reduce((acc, type) => ( ...acc, [type]: type ), );
这里我们使用与reduce
相同的代码,但我们将其隐藏在它自己的函数中,并使用单个overload call signature 来表示预期的输出类型。现在,我们可以像这样得到b
:
const b = keyArrayToSameValueAsKeys(a);
b.ONE; // property type is "ONE"
b.TWO; // property type is "TWO"
b.THREE; // property type is "THREE"
如果你把stringTuple()
和keyArrayToSameValueAsKeys()
放在一个库中,用户可以毫不费力地使用它们:
const otherObject = keyArrayToSameValueAsKeys(stringTuple("x", "y", "z"));
// const otherObject: X: "X", Y: "Y", Z: "Z"
或者您可以像这样将它们合并在一起:
function keysToSameValueAsKeys<T extends string[]>(...keys: T): [K in T[number]]: K ;
function keysToSameValueAsKeys(keys: string[]): [k: string]: string
return keys.reduce((acc, type) => ( ...acc, [type]: type ), );
然后在一次调用中获取输出,如下所示:
const lastOne = keysToSameValueAsKeys("tic", "tac", "toe");
// const lastOne: tic: "tic", tac: "tac", toe: "toe";
好的,希望对您有所帮助。祝你好运!
【讨论】:
嗨,当密钥被硬编码时,您的解决方案有效。有没有办法使用动态导入,比如webpack.context
。例如:const icons = require.context('../icons', false, /\.svg$/); const b = keysToSameValueAsKeys(icons.keys().map((k: string) => icons(k).default.id));
,此示例代码不会使 ts 理解键,因此不会给出任何自动完成提示。谢谢:)
这个例子中icons
的推断类型是什么?如果您真的需要在这里找到答案,您可能想提出一个新问题,因为 cmets 不是发布代码或吸引更多人关注您的问题的好地方。
是的,当然。这是我对 github demo repo ***.com/questions/55571250/… 的问题。感谢您的帮助:-)【参考方案2】:
如果您提前知道数组中的内容,您可以执行以下操作:
type myType = 'ONE' | 'TWO' | 'THREE';
const a: myType[] = ['ONE', 'TWO', 'THREE'];
const b: [key in myType]: myType = a.reduce< [key in myType]: myType >((acc, type) => ( ...acc, [type]: type ), <any>);
// correct intellisense
b.ONE === 'ONE'
【讨论】:
谢谢,这很酷。我猜这意味着如果你不提前知道是不可能的? 我想。因为这里我们声明了一个带有 union 'ONE' | 的类型。 '两个' | '三'。但是在运行时我们不再有类型,所以如果它可以包含任何字符串,就不可能从数组中推断它们。【参考方案3】:来到这里寻找更好的答案。这是我想出的,效果很好:
const makeStyles = <T extends string = string>(styles: Record<T, string>): Record<T, string> =>
return styles;
const classes = makeStyles(
hello: 'hello world',
another: 'something else'
)
/**
* now typescript will infer the keynames that appear in the object passed
* to makeStyles
*/
console.log(classes)
【讨论】:
以上是关于TypeScript 是不是可以从动态对象中推断出键?的主要内容,如果未能解决你的问题,请参考以下文章
在 TypeScript 中,如何在具有多个类型参数的方法中推断出泛型类型参数?