关于lambda:TypeScript,如何使用带类型的表达式传递公共字段名称

TypeScript, how to pass public field names with typed expression

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

例如。 带有魔术弦:
searchFilter(model, 'searchParameter', ['id', 'name'])

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

作为参考,此函数看起来像这样:

带有魔术弦:(或我尝试输入的方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
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#中那样一个相关的"函数表达式"来提取被调用的属性,以获取其名称)

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
 }


您无法摆脱字符串,打字稿中还没有诸如nameof属性之类的东西(尚未)。

但是,您可以做的是键入某些内容作为另一种类型的键。

像这样。

1
2
3
4
5
6
interface Model {
    a: string,
    b: number
}

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

结果是:

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

您可以输入如下类型的属性数组:

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

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


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

1
2
3
4
5
6
7
8
9
10
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)

可以创建与属性同名的闭包方法并调用所需的闭包方法:

1
2
3
4
5
6
7
8
9
10
11
12
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而不是classes


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

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

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

下面在左侧显示了已编译的JavaScript输出,在右侧显示了等效的TypeScript。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);

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


经过一些调试后,我确实找到了答案,但如有需要,请随时提供更好的答案。下面的代码和说明...:

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

  • function (v) { v.id; }
  • 在第一个.slice()步骤之后,它变为v.id;
  • 在第二个.slice()步骤之后,它变为id
  • 一些警告!这不涉及嵌套的属性,其性能可能也不理想。对于我的用例来说,这足够了,但是欢迎任何想法或改进。现在,我将不再搜索-因为用例不需要它。

    代码的要点在这里:

    1
    2
    3
    4
    5
    6
    7
    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());
        });

    在角度服务中实现,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      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;
      }

    界面易于使用

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