【问题标题】:'compile-time' way to get all property names defined interface'compile-time' 获取所有属性名称定义接口的方法
【发布时间】:2015-07-24 07:37:59
【问题描述】:

我想创建一个通用 TypeScript 类,用于呈现(作为 HTML 列表)实现特定 interface 的对象数组。

例如

class GenericListRenderer<T> {
  items: T[];

 constructor(listItems: T[], className?: string){
      this.items = listItems;
      ...
    }

    private getPropertyNames(): string[]{

    // What is the best way to access all property names defined in
    // TypeScript interface 'T' that was used in this generic?
    ...   
    }

    render(){
      var propNames: string[] = this.getPropertyNames();
      // render list with each item containing set of all 
      // key(prop name)/value pairs defined by interface 'T'  
      ...

    }
}

问:获取指定 () TypeScript 接口中定义的所有属性名称的“编译时”列表的最佳方法是什么?

与 C++ 模板一样,我相信 TypeScript 可以在“编译时”解析这些泛型,此时 TypeScript 类型信息(如提供给用于实例化特定对象的泛型的接口)很容易获得。

由于可能提供了所有必需的类型信息,我只是好奇是否有一个 TypeScript 扩展/工具可用于访问此信息而无需对“vanilla”Javascript 对象进行过多的运行时过滤——这可能是有问题的不明确的继承问题(例如,如果使用运行时、泛型、Javascript (obj.hasOwnProperty(prop)) 来过滤属性,则可能会过滤掉所需的 TypeScript 继承的接口属性)。

这种有用的属性自省潜力可以在“编译时”期间使用 TypeScript 的类型元数据超集明确解决,而不是在所有这些类型信息都被丢弃时尝试在翻译的 Javascript 中解析此信息。

如果存在标准 (TypeScript) 方法,我不希望使用可能不完美的 Javascript hack 来“重新发明轮子”。

【问题讨论】:

标签: typescript generics interface


【解决方案1】:

您可以通过拨打电话获取您需要的所有信息

let test = Object.keys(instanceOfObject)

如果你记录这个 console.log(test),你会得到类似的东西:

0: "user_id"
...
26: "address"
length: 27

您可以访问此对象的所有信息,例如 test.length 等。

发件人:https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

【讨论】:

  • 只有在instanceOfObject 拥有接口的所有属性时才如此。因此,如果 instanceOfObject 是“默认对象”,这将起作用。
【解决方案2】:

这是不可能在运行时检索该信息的,并且除非您在编译期间存储它们,否则它们将永远不可能默认。因此,解决方案是使用ReflectDecorators 进行反思。

Here 是一篇很好的文章,涵盖了在运行时检索编译时元数据的问题。简而言之:您向要保留描述的界面添加一个装饰器,这个装饰器将被转换为一个 JSON 对象,该对象将存储到代码本身中。在运行时,您将能够检索具有所有接口数据的 JSON 对象。这现在是实验性的(2016 年 2 月 11 日),但效果很好。

注意:默认情况下它永远不会的原因基本上是 TS 的设计选择,不要用元数据重载 js 代码(与 Dart 不同)。

【讨论】:

  • “除非你在编译期间存储它们”:作为一个松散的实现,我在MyClass 中创建了一个static PROP_NAMES : string[] = ['prop1', 'prop2', 'propN'];。然后我做一个MyClass.PROP_NAMES.forEach(key=&gt; { /* do something */ });
【解决方案3】:

这可以通过使用https://github.com/Microsoft/TypeScript/pull/13940 引入的自定义转换器来实现,该转换器在 typescript >= 2.4.1 中可用。

我的 npm 包 ts-transformer-keys 就是一个很好的例子。

import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

【讨论】:

  • 这看起来不错,但它似乎需要一个特殊的构建过程——对吗?如果还有一个 optionalKeys 函数也很好 - 它返回了可选值。这将使以类型安全的方式验证所需的道具运行时成为可能。
  • 对。请求插件支持自定义转换器时出现问题:github.com/Microsoft/TypeScript/issues/14419。使用自定义转换器实现optionalKeys 函数并不难。
  • @kimamula 看起来很有趣,你知道它是在哪个版本推出的吗?
  • @TomaszSmykowski 我忘了更新我的答案。该功能是在 TypeScript 2.4.1 中引入的。
  • 重要的是要知道keys&lt;type&gt;(); 应该在与定义的接口相同的文件中调用。花了几个小时.. :)
【解决方案4】:

在运行时,所有类型信息都会被删除,因此您能做的最好的事情就是枚举其中一个对象的属性。这将返回所有属性,即使是那些不在指定接口上的属性。

class GenericListRenderer<T> {

    constructor(private items: T[], private className?: string){

    }

    private getPropertyNames(): string[] {
        var properties: string[] = [];

        if (this.items.length > 0) {
            for (var propertyName in this.items[0]) {
                console.log(propertyName);
                properties.push(propertyName);
            }
        }

        return properties;
    }

    render(){
      var propNames: string[] = this.getPropertyNames();
    }

}

class Example {
    constructor(public name: string) {

    }
}

var example = new Example('Steve');

var a = new GenericListRenderer<Example>([example]);

a.render();

还有Object.keys(),它把所有的属性都还给你,虽然它只在IE9及更高版本中受支持。

如果您可以为您想要对属性执行的操作提供更多用例,则可以使用属性或其他机制为您提供替代解决方案。

【讨论】:

  • 这就是问题所在:似乎运行时解决方案不会利用丢弃的字体元数据,这让我相信只有“编译时”TypeScript 解决方案才能解决问题。因为我不是 TypeScript 大师:我只是想知道这样有用的功能是否已经存在。
猜你喜欢
  • 2020-02-28
  • 1970-01-01
  • 1970-01-01
  • 2013-06-19
  • 2014-10-27
  • 1970-01-01
相关资源
最近更新 更多