【问题标题】:nameof keyword in TypescriptTypescript中的nameof关键字
【发布时间】:2018-11-01 08:38:55
【问题描述】:

正如我所见,TypeScript 中没有原生的 nameof-keyword like C# has。但是,出于与 C# 中存在相同的原因,我希望能够以类型安全的方式引用属性名称。

当使用 jQuery 插件 (Bootstrap-Tagsinput) 或其他需要配置属性名称的库时,这在 TypeScript 中特别有用。

它可能看起来像:

const name: string = nameof(Console.log);
// 'name' is now equal to "log"

name 的分配也应该在 Console.log 被重构和重命名时发生变化。

到目前为止,在 TypeScript 中使用此类功能的最接近的方法是什么?

【问题讨论】:

    标签: typescript types


    【解决方案1】:

    我认为我们经常需要更多:通过编译时验证在运行时获取类属性名称。重命名属性将更改 nameOf 表达式。这是一个非常有用的功能:

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

    用法示例(无字符串!):

    if (update.key !== nameOf((_: SomeClass) => _.someProperty)) {
       // ...                               
    }
    

    现有实例的示例:

    export interface I_$<T> {
        nameOf<V extends T[keyof T]>(f: (x: T) => V): valueOf<{ [K in keyof T]: T[K] extends V ? K : never }>;
    }
    
    export function _$<T>(obj: T) {
        return {
            nameOf: (f: (x: any) => any) => {
                return nameOf(f);
            }
        } as I_$<T>;
    }
    

    用法:

    let obj: SomeClass = ...;
    _$(obj).nameOf(x => x.someProperty);
    or _$<SomeClass>().nameOf(x => x.someProperty);
    

    解析为“someProperty”。

    【讨论】:

    • 这里发生了什么:export function nameOf&lt;T, V extends T[keyof T]&gt;(f: (x: T) =&gt; V): valueOf&lt;{ [K in keyof T]: T[K] extends V ? K : never }&gt;
    • 这只是在编译时支持正确名称提取的技巧(在运行时使用代理)
    【解决方案2】:

    如果你只需要以字符串的形式访问属性,你可以像这样安全地使用代理:

    function fields<T>() {
        return new Proxy(
            {},
            {
                get: function (_target, prop, _receiver) {
                    return prop;
                },
            }
        ) as {
            [P in keyof T]: P;
        };
    };
    
    interface ResourceRow {
        id: number;
        modified_on_disk: Date;
        local_path: string;
        server_path: string;
    }
    
    const f = fields<ResourceRow>();
    
    // In this example I show how to embed field names type-safely to a SQL string:
    const sql = `
    CREATE TABLE IF NOT EXISTS resource (
        ${f.id}               INTEGER   PRIMARY KEY AUTOINCREMENT NOT NULL,
        ${f.modified_on_disk} DATETIME       NOT NULL,
        ${f.local_path}       VARCHAR (2048) NOT NULL UNIQUE,
        ${f.server_path}      VARCHAR (2048) NOT NULL UNIQUE
    );
    `;
    

    【讨论】:

      【解决方案3】:

      正如您已经说过的,TypeScript 2.8 版没有内置功能。但是,有一些方法可以获得相同的结果:

      选项 1:使用库

      ts-nameof 是一个提供与 C# 完全相同的功能的库。有了这个,你可以这样做:

      nameof(console); // => "console"
      nameof(console.log); // => "log"
      nameof<MyInterface>(); // => "MyInterface"
      nameof<MyNamespace.MyInnerInterface>(); // => "MyInnerInterface"
      

      ts-simple-nameof 提供了另一种选择。它基本上解析一个字符串化的 lambda 以找出属性名称:

      nameof<Comment>(c => c.user); // => "user"
      nameof<Comment>(c => c.user.posts); // => "user.posts"
      

      选项 2:定义辅助函数

      您可以轻松定义自己的 nameof 来添加类型检查,但它不会自动重构,因为您仍然需要输入字符串文字:

      const nameof = <T>(name: keyof T) => name;
      

      它将返回传递的属性名称,但当属性名称在类型T 上不存在时会生成编译时错误。像这样使用它:

      interface Person {
          firstName: string;
          lastName: string;
      }
      
      const personName1 = nameof<Person>("firstName"); // => "firstName"
      const personName2 = nameof<Person>("noName");    // => compile time error
      

      Credits and more information about this

      使用 TypeScript 2.9+ 更新辅助函数

      keyof T 类型现在不仅解析为字符串,还解析为 string | number | symbol (ref)。如果您仍只想解析字符串,请改用此实现:

      const nameof = <T>(name: Extract<keyof T, string>): string => name;
      

      【讨论】:

      • 这是另一个添加到列表中的努力 - github.com/IRCraziestTaxi/ts-simple-nameof
      • 我真的很喜欢编译时的 ts-nameof 转换。节省了其他解决方案提供的解析表达式等可能的开销。
      • 请注意,如果您的对象也被定义为具有索引签名,则这不起作用,因为这会使编译器接受任何键。
      猜你喜欢
      • 2016-07-08
      • 2016-06-18
      • 2017-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-06
      相关资源
      最近更新 更多