我想创建一个通用的 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 扩展/工具可用于访问此信息,而无需对“普通”Javascript 对象进行过多的运行时过滤——这可能会因继承不明确而出现问题问题(例如,如果使用运行时、通用、Javascript (obj.hasOwnProperty(prop)) 来过滤属性,则所需的 TypeScript 继承接口属性可能会被过滤掉)。
这种有用的属性自省潜力可以在“编译时”使用 TypeScript 的类型元数据超集明确解决,而不是在所有此类类型信息都被丢弃时尝试在翻译后的 Javascript 中解析此信息。
如果存在标准(TypeScript)方法,我讨厌用可能不完美的 Javascript hack 来“重新发明轮子”。
这可以通过使用 https://github.com/Microsoft/TypeScript/pull/13940 引入的自定义转换器来实现,该转换器在 typescript >= 2.4.1 中可用。
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']
不可能在运行时检索该信息,并且默认情况下它们永远不会成为可能,除非您在编译期间存储它们。因此,解决方案是使用 ReflectDecorators 进行反射。
Here 是一篇优秀的文章,涵盖了在运行时检索编译时元数据的问题。简而言之:您向想要保留描述的界面添加一个装饰器,该装饰器将被转换为 JSON 对象,该对象将存储到代码本身中。在运行时,您将能够检索包含所有接口数据的 JSON 对象。现在这是实验性的(2016 年 2 月 11 日),但效果很好。
注意:默认情况下永远不会的原因基本上是 TS 的设计选择,不要使用元数据重载 js 代码(与 Dart 不同)。
在运行时,所有类型信息都会被删除,因此您能做的最好的事情就是枚举其中一个对象的属性。这将为您返回所有属性,甚至是那些不在指定接口上的属性。
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 及更高版本中受支持。
如果您可以为您想要使用属性执行的操作提供更多用例,则可能会使用属性或其他机制为您提供替代解决方案。
您可以尝试 Accelatrix,因为它包含一个在运行时工作的 JavaScript 类型自省系统:
https://www.nuget.org/packages/Accelatrix
https://www.npmjs.com/package/accelatrix
https://github.com/accelatrix/accelatrix
JavaScript 的类型系统得到增强,包括四个基本操作:
- GetHashCode()
- GetType()
- Equals()
- ToString()
您现在可以像在 C# 中一样在运行时处理 JavaScript 中的类,例如:
var myDog = new Bio.Mammal(8);
var myCat = new Bio.Feline(8, 9);
var timeIsSame = (new Date()).Equals(new Date()); //true
var areEqual = myDog.Equals(myCat); // false
var myCatType = myCat.GetType(); // Bio.Feline
var myCatBaseType = myCat.GetType().BaseType; // Bio.Mammal
var isAnimal = myCat.GetType().IsAssignableFrom(Bio.Animal); // true
var enums = Bio.TypesOfLocomotion.GetType(); // Accelatrix.EnumType
// 示例类:
export namespace Bio
{
export enum TypesOfLocomotion
{
Crawl,
Swim,
Walk,
Fly,
}
abstract class LivingBeing
{
public isExtinct = false;
}
export abstract class Eukaryotes extends LivingBeing
{
private locomotion: TypesOfLocomotion = null;
public get Locomotion(): TypesOfLocomotion
{
return this.locomotion;
}
public set Locomotion(value: TypesOfLocomotion)
{
this.locomotion = value;
}
}
export class Animal extends Eukaryotes
{
public isAnimal = true;
public constructor()
{
super();
}
}
export class Mammal extends Animal
{
private readonly numberOfTits: number;
public constructor(numberOfTits: number)
{
super();
this.numberOfTits = numberOfTits;
}
public get NumberOfTits(): number
{
return this.numberOfTits;
}
public SayHello(): string
{
return "Hello";
}
}
export class Feline extends Mammal
{
private readonly numberOfLives: number;
public constructor(numberOfTits: number, numberOfLives: number)
{
super(numberOfTits);
this.numberOfLives = numberOfLives == null ? 9 : numberOfLives;
this.Locomotion = TypesOfLocomotion.Walk;
}
public get NumberOfLives(): number
{
return this.numberOfLives;
}
}
}