TypeScript,如何使用类型化表达式传递公共字段名称

时间:2017-06-24 16:52:24

标签: typescript lambda

我正在试图弄清楚如何传递给定对象的属性(或字段名)数组,而不使用所谓的魔术字符串 - 因为拼写错误很容易!本质上,我正在寻找与csharp的“Expression<>”相关的东西。我想。

E.g。用魔术字符串: searchFilter(model, 'searchParameter', ['id', 'name'])

E.g。键入,或我想如何调用该函数: searchFilter(model, 'searchParameter', [m => m.id, m => m.name])

作为参考,这个函数看起来有点像这样:

使用魔术字符串(或者我尝试键入的方式)

private searchFilter(mode: Model, q: string, properties: string[]): boolean {
   if (q === '') return true;

   q = q.trim().toLowerCase();

   for (let property of properties) {
     if (vacature[property.toString()].toString().toLowerCase().indexOf(q) >= 0) {
       return true;
     }
  }

  return false;
}

键入:(或者我是如何尝试键入的,但是这个当然只返回函数..我需要一个相关的'函数表达式',如在C#中提取被调用的属性,得到它的名字)

private searchFilter(mode: Model, q: string, propertySelector: ((x: Model) => any | string)[]): boolean {
   if (q === '') return true;

   q = q.trim().toLowerCase();

   for (let property of propertySelector) {
     if (vacature[property.toString()].toString().toLowerCase().indexOf(q) >= 0) {
       return true;
     }
  }

  return false;
 }

5 个答案:

答案 0 :(得分:2)

你无法摆脱字符串,因为在typescript(还)中没有 nameof 属性。

但你可以做的是输入某种东西作为另一种类型的钥匙。

喜欢这个。

interface Model {
    a: string,
    b: number
}

function searchFilter(model: Model, q: keyof Model) { }

这导致:

searchFilter(null, 'a') // works
searchFilter(null, 'b') // works
searchFilter(null, 'c') // error c is not a property of Model

您可以键入类似以下类型的属性数组:

function searchArray(model: Model, q: string, properties: Array<keyof Model>) { }

searchArray(null, 'blabla', ['a', 'b'])

答案 1 :(得分:1)

Nameof本身不可用,但已使用第三方库复制了该功能。

您可以通过使用第三方库(https://www.npmjs.com/package/ts-nameof)来实现nameof功能。您可以在此处查看源代码:https://github.com/dsherret/ts-nameof

在这种情况下,库根据您要使用的对象名称级别提供了许多选项,例如变量本身的名称,方法的名称,方法的名称及其包含的类等等(摘自图书馆文档)。

下面显示左侧的已转换JavaScript输出和右侧的TypeScript等效项。

console.log("console");             // console.log(nameof(console));
console.log("log");                 // console.log(nameof(console.log));
console.log("console.log");         // console.log(nameof.full(console.log));
console.log("alert.length");        // console.log(nameof.full(window.alert.length, 1));
console.log("length");              // console.log(nameof.full(window.alert.length, 2));
console.log("length");              // console.log(nameof.full(window.alert.length, -1));
console.log("alert.length");        // console.log(nameof.full(window.alert.length, -2));
console.log("window.alert.length"); // console.log(nameof.full(window.alert.length, -3));

"MyInterface";                      // nameof<MyInterface>();
console.log("Array");               // console.log(nameof<Array<MyInterface>>());
"MyInnerInterface";                 // nameof<MyNamespace.MyInnerInterface>();
"MyNamespace.MyInnerInterface";     // nameof.full<MyNamespace.MyInnerInterface>();
"MyInnerInterface";                 // nameof.full<MyNamespace.MyInnerInterface>(1);
"Array";                            // nameof.full<Array<MyInterface>>();
"prop";                             // nameof<MyInterface>(o => o.prop);

这些字符串在转换时被替换,因此不应该有任何运行时性能损失。

答案 2 :(得分:1)

可以使用与属性相同的名称创建闭包方法,并调用所需的一个:

class Foo {
    public bar: string = null; // property has to be initialized
}

function getPropertyName<T>(TCreator: { new(): T; }, expression: Function): string {
    let obj = new TCreator();
    Object.keys(obj).map(k => { obj[k] = () => k; });
    return expression(obj)();
}

let result = getPropertyName(Foo, (o: Foo) => o.bar);
console.log(result); // Output: `bar`

同样的方法,但objects而不是classeshere

答案 3 :(得分:0)

经过一些调试,我确实找到了答案,但如果你有任何答案,请随时给出更好的答案。代码&amp;以下解释......:

由于您通过propertySelectors: ((x: T) => any | string)[]传递了一系列函数,因此可以删除每个函数的主体。然后,您删除每个函数的return.;部分,因此最终只得到属性名称。例如:

  1. function (v) { v.id; }
  2. 在第一个.slice()步骤之后变为v.id;
  3. 在第二个.slice()步骤之后变为id
  4. 一些警告!这不包括嵌套属性,并且这种性能可能也不理想。 Hower for my usecase这已经足够了,但欢迎任何想法或改进。现在我不再搜索 - 因为我的用例不需要它。

    代码的要点在这里:

    let properties: string[] = [];
        propertySelector.forEach(propertySelector => {
          const functionBody = propertySelector.toString();
          const expression = functionBody.slice(functionBody.indexOf('{') + 1, functionBody.lastIndexOf('}'));
          const propertyName = expression.slice(expression.indexOf('.') + 1, expression.lastIndexOf(';'));
          properties.push(propertyName.trim());
        });  
    

    在角度服务中实现它看起来像这样:

    import { Injectable } from '@angular/core';
    import { IPropertySelector } from '../../models/property-selector.model';
    
    @Injectable()
    export class ObjectService {     
        extractPropertyNames<T>(propertySelectors: IPropertySelector<T>[]): string[] {
            let propertyNames: string[] = [];
    
            propertySelectors.forEach(propertySelector => {
                const functionBody = propertySelector.toString();
                const expression = functionBody.slice(functionBody.indexOf('{') + 1, functionBody.lastIndexOf('}'));
                const propertyName = expression.slice(expression.indexOf('.') + 1, expression.lastIndexOf(';'));
                propertyNames.push(propertyName);
            });
    
            return propertyNames;
        }
    }
    

    并在注入服务的组件的方法中使用这样的方法:

      private searchFilter(model: Model, q: string, propertySelectors: IPropertySelector<Model>[]): boolean {
        if (q === '') return true;
    
        q = q.trim().toLowerCase();
    
        if (!this.cachedProperties) {
          this.cachedProperties = this.objectService.extractPropertyNames(propertySelectors);
        }
    
        for (let property of this.cachedProperties) {
          if (model[property].toString().toLowerCase().indexOf(q) >= 0) {
            return true;
          }
        }
    
        return false;
      }
    

    易于使用的界面

    export interface IPropertySelector<T> {
        (x: T): any;
    }
    

答案 4 :(得分:0)

我喜欢基于lambda的方法(但在足够/可能的情况下,大多数时候应该使用密钥):

type valueOf<T> = T[keyof T];
function nameOf<T, V extends T[keyof T]>(f: (x: T) => V): valueOf<{ [K in keyof T]: T[K] extends V ? K : never }>;
function nameOf(f: (x: any) => any): keyof any {
    var p = new Proxy({}, {
        get: (target, key) => key
    })
    return f(p);
}
// Usage:
nameOf((vm: TModel) => vm.prop)