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) =&gt; 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笔记

在 TypeScript 中,如何在具有多个类型参数的方法中推断出泛型类型参数?

TypeScript在静态实现中推断出更通用的类型

Typescript 根据同一对象中的属性推断函数的对象键

为啥 TypeScript 在使用 concat 减少数组时会推断出“从不”类型?

TypeScript 中的类型推断在特殊用例中无法正常工作