Typescript: 建议:添加 nameof compile-time operator 将属性和函数名称转换为字符串

创建于 2014-12-30  ·  174评论  ·  资料来源: microsoft/TypeScript

我希望看到nameof运算符被考虑用于 Typescript。

此功能刚刚添加到 C# description ,它是 Javascript 中常见问题的优雅解决方案。

在编译时, nameof将其参数(如果有效)转换为字符串。 这使得推理需要跨重构匹配变量或属性名称、防止拼写错误和其他类型安全功能的“魔法字符串”变得更加容易。

引用链接的 C# 文章:

为此目的使用普通字符串文字很简单,但容易出错。 你可能拼错了,或者重构可能让它变得陈旧。 nameof 表达式本质上是一种花哨的字符串文字,编译器会检查您是否具有给定名称的内容,并且 Visual Studio 知道它指的是什么,因此导航和重构将起作用:

(if x == null) throw new ArgumentNullException(nameof(x));

再举一个例子,假设你有这个 Person 类:

class Person {
    firstName: string
    lastName: string
}
var instance : Person = new Person();

如果我有一个 API 要求我通过字符串指定一个属性名称(在 JS 中很常见),我将被迫做这样的事情:

   someFunction(personInstance, "firstName");

但是,如果我拼错了firstName ,就会出现运行时错误。
所以这是类型安全的等价物:

   someFunction(personInstance, nameof(Person.firstName));
Suggestion Waiting for TC39

最有用的评论

为什么过了这么久,TS 仍然没有机制(例如nameof )来消除MAGIC STRINGS

我不明白为什么这个线程有这么多的乞求、理由和用例。 这很简单 - 这应该是重中之重。 是JS最可怕的地方,TS还没有解决。

所有174条评论

另请参阅 #394 和 #1003。

修复魔法字符串问题是论坛上的常见要求。 希望尽快看到正式的提案。

是的,把它当作一个骗子来结束,足以说我们肯定会感受到人们在这里的痛苦。

我不认为这是对任何提到的问题的欺骗。 这对于支持高级缩小和元编程至关重要。 目前没有办法在缩小后如何获取成员字符串。
虽然我不知道如何在不与其他功能冲突的情况下将它添加到语言中......

在 C# 中,这以其他方式工作 - 您会得到未缩小的字符串。 在打字稿中,我需要缩小的字符串。 所以也许这实际上可能是不同的提议:-) 因为未缩小也很好。 只是一些想法...

同意。 看起来不像重复。 nameof 运算符应该被认为是一个非常好的主意。 也可能“容易”实施。
我使用带有 angularjs 的打字稿,我喜欢在我的控制器/指令类中创建静态字符串常量,当我在 angular 模块中注册这些类时使用这些常量。 我想使用 nameof(MyDirectiveClass) 代替对这些名称进行硬编码。
这与我们之前在 WPF 中的 PropertyChanged 事件(在 nameof/CallerMemberName 之前)重命名时遇到的问题相同。

好点 - 其他问题并没有完全涵盖所讨论的内容

:竖起大拇指:

我在工作中的一个项目中使用 Typescript,并且正在放入一个运行时鸭子类型检查器。 用例是反序列化 URL 参数。

如果此功能在语言中,这就是我期望语法的工作方式:

class Foo {
    prop: string;
}

function isFoo(obj: Foo): boolean;
function isFoo(obj: any) {
    return (typeof obj === 'object') && 
              (obj !== null) && 
              (nameof(Foo.prop) in obj);
}

// Output for isFoo()
function isFoo(obj: any) {
    return (typeof obj === 'object') && 
              (obj !== null) && 
              ('prop' in obj);
}

这是一个正确的假设吗?

@frodegil ,这将是一个很棒的用例。 将减少我日常工作流程中的大量代码异味和重复复制/粘贴。

@Bobris ,为了我的示例的目的,人们希望缩小的字符串,我认为如果缩小进入 tsc,这将是理想的行为。

@bryanerayner ,您的假设是正确的。 nameof 运算符仅在编译期间用于将类型信息转换为字符串。

module pd {
   export class MyAngularControllerClass {
      public static IID : string = nameof(MyAngularControllerClass);  // "MyAngularControllerClass"
      public static $inject: string[] = [Model1.IID, Model2.IID, "$scope"];
      constructor(model1: Model1, model2:Model2, $scope:angular.IScope) {
      }
      public get nameOfThisGetter() : string {
         return nameof(this.nameOfThisGetter);    // "nameOfThisGetter";
      }
   }
   angular.module(nameof(pd)).controller(MyAngularControllerClass.IID, MyAngularControllerClass);
   // angular.module("myapp").controller("OldName", MyAngularControllerClass); < out of sync
}

在重构过程中很容易让这些硬编码的字符串不同步

这太棒了。

确实很棒。 这样的操作员可以做很多事情。

我们想看看#394 和#1003 着陆后的感受。 我们有可能在类型系统中解决这些用例,而不必求助于添加新的表达式级语法。

与此同时,最好收集那些提案无法充分解决的任何用例。

好吧, @frodegil部分描述的关于管理以某种方式注册的组件的情况(大多数情况下与注册的Function/Class名称相同),就像 Angular 的情况一样,是就我所见,这种情况既不能由 #394 的memberof也不能由 #1003 的字符串文字类型处理(不过我真的很期待后者!)。

我同意并理解如何添加新的 sintax 元素是不受欢迎的,甚至违背了 typescript 的主要目标(成为 javascript 的超集),但我仍然认为这个提议非常好。

另一个小案例

出于性能和一致性原因,我使用 pouchdb 并制作自己的文档 ID。 这些 docId 是使用几个字段生成的,其中之一基于要保存的模型的名称,例如:

现在我需要在我的所有模型上维护一个名为 className 的属性:

class Person {
  className = 'Person';

  _id: string;
  $_dateCreated: number;
}

var p:Person;
p._id = `${this.className}_${window.appMetadata.cordovaUuid}__${this.$_dateCreated}__${window.uuid.v4()}`
pouchdb.put(p)

可以变成:

class Person {
  _id: string;
  $_dateCreated: number;
}

var p:Person;
p._id = `${nameof Person}_${window.appMetadata.cordovaUuid}__${this.$_dateCreated}__${window.uuid.v4()}`
pouchdb.put(p)

可预见的问题

我可以看到在处理泛型或继承时弄清楚某些事情的nameof变得有点困难,所以我同意这个功能需要很多思考。

薄的替代品

考虑到所有处理属性名称的情况都可以由 #394 或 #1003 完全处理,我会说getClass()getClassName()的暴露(参见此处的示例)将解决我知道的其余情况无需创建新的 sintax 元素。 例如,它也可以是一个核心装饰器。

在 Angular2 中,#394、#1003、getClass() 和 GetClassName() 都没有帮助的用例是:

    @Component({
        selector: 'my-selector',
        template: `<h1>{{${nameof MyComponent.prototype.title}}}</h1>`
    })
    class MyComponent { 
        public title = "Hellow world";
    }

这将使代码重构证明,但它也降低了它的可读性,所以我不确定这是否是一个好主意。 只是一个想法。

在此处添加建议以记住nameof的枚举,作为 toString'ing 枚举的快捷方式:

nameof(MyModule.Enums.FooEnum.Bar) === "Bar"

将编译为:

MyModule.Enums.FooEnum[MyModule.Enums.FooEnum.Bar] === "Bar"

要不就:

"Bar" === "Bar"

一个很好的用例是单元测试,其中SinonJS用于监视/存根。 使用建议的nameof关键字,可以传递要监视或存根的方法和属性的名称。 重构方法名称时,单元测试不会因为需要更新的字符串而中断。

var obj = new MyClass();
sinon.stub(obj, 'methodName', () => null); // Prone to break.
var obj = new MyClass();
sinon.stub(obj, nameof(MyClass.methodName), () => null);

或者,如果存根支持传入谓词来解析名称:

var obj = new MyClass();
function stub<T>(targetObject: T, targetMethodPredicate: (T)=>string, methodStub: Function){}
sinon.stub(obj, x => nameof(x.methodName), () => null);

我有一个构建 SQL 字符串的用例。 这是一个代码片段:

interface User {
    id: string;
    name: string;
    birthday: Date;
}

// Current implementation
var sql = `SELECT id,name FROM Users`;

// Desired implementation
var sql = `SELECT ${nameof(User.id)},${nameof(User.name)} FROM ${nameof(User)}s`;

nameof()特性将使这种类型安全并且重构安全。 例如,将 User 表重命名为 Account 将是一个快速重构,而不是像这样搜索代码和丢失动态字符串。

我同意这应该是一个编译时转换,就像在 C# 中一样,所以我们可以刻录类型名称。

@瑞安卡瓦诺
五个月后:

1003 已经登陆,但在许多情况下没有帮助(见下文)。

394 被关闭以支持 #1295。 后者似乎离着陆还很远,虽然它解决了问题的一个有趣部分(打字),但对 string/nameof 部分没有帮助。

也许是时候重温了?

字符串很糟糕,因为您无法“查找所有引用”,也无法“重构/重命名”,而且它们容易出现拼写错误。

使用 Aurelia 的一些示例:

let p: Person;

// Observe a property for changes
let bindingEngine: BindingEngine;
bindingEngine.observeProperty(p, 'firstName')
             .subscribe((newValue: string) => ...);

class Person {
  firstName: string;

  // Declare a function to call when value changes
  @bindable({ changeHandler: 'nameChanged'})
  lastName: string;

  nameChanged(newValue: string, oldValue: string) { ... }

  // Declare computed property dependencies
  @computedFrom('firstName', 'lastName')
  get fullName() {
    return `${firstName} ${lastName}`;
  }
}

// Declare dependencies for validation
validation.on(p)
  .ensure('firstName').isNotEmpty()
  .ensure('fullName', config => config.computedFrom(['firstName', 'lastName'])).isNotEmpty();

这里有五个不同的 API,它们都可以从nameof运算符中受益。

有效积分。 我会再次提出它(注意:我们现在非常忙于一些即将开展的工作,因此建议分类有点积压)

抱歉我“Ctrl-Entered”我的帖子太快了。 我添加了几个可以从中受益的真实代码示例。

更复杂的是,上面的一些 API 也接受路径。 所以@computedFrom('p.address.city')是有效的,支持它也很好......

在另一个问题的评论中,我从 C# 中提出了某种有限的 _Expressions_。

对于此线程中的大多数示例,它们将是比nameof更好的替代方案:

  • 他们可以帮助输入结果;
  • 它们支持更复杂的成员访问(嵌套、索引器);
  • 他们为您提供了一个变量,在某些情况下,您不会在nameof范围内。

最后一点的缺点是表达式不会涵盖以下情况:

function f(x: number) {
  let s = nameof(x);
}

但这并不像其他用例那样常见。

我正在复制另一个问题的想法描述:


假设我们有一种特殊的字符串来表示表达式。
我们称之为type Expr<T, U> = string
其中T是起始对象类型, U是结果类型。

假设我们可以通过使用一个 lambda 来创建Expr<T,U>的实例,该 lambda 表达式接受一个T类型的参数并对其执行成员访问。
例如: person => person.address.city
发生这种情况时,整个 lambda 被编译为一个字符串,其中包含对参数的任何访问,在这种情况下: "address.city"

您可以改用普通字符串,这将被视为Expr<any, any>

在语言中使用这种特殊的Expr类型可以实现以下内容:

function pluck<T, U>(array: T[], prop: Expr<T, U>): U[];

let numbers = pluck([{x: 1}, {x: 2}], p => p.x);  // number[]
// compiles to:
// let numbers = pluck([..], "x");

这基本上是 C# 中使用表达式的有限形式。

https://github.com/basarat/typescript-book/issues/33至少有一些解决方法(尽管由于 ASP.Net 的简单缩小,我在返回后遇到了没有尾随 ';' 的问题)

Big :+1: :100: 来自我的支持。

由于上述许多其他人给出的原因,我正在使用变通方法 - 我想要一些对重构友好的东西。 接受字符串以标识 Javascript 对象上某些属性的名称的 API 很多。

这是一个真实世界的例子,其中nameof可能非常有用。

我有一个类作为在工作人员中远程执行的功能的“控制器”。 控制器类的结构完全反映了在工作器中执行的实际实现类。

我需要在一个字符串中安全地引用控制器中每个方法的名称,然后将其存储在发送给工作人员的请求消息中。 但是,当前的最佳解决方案不是非常类型安全:

export class LocalDBWorkerController<V> extends LocalDBBase<V> {
        initializeWorkerDB(options: BrowserDBOptions): Promise<void> {
            return this.executeInWorker("initializeWorkerDB", [options]);
        }

        getEntry(key: string): Promise<DBEntry<V>> {
            return this.executeInWorker("getEntry", [key]);
        }

        getEntries(keys: string[]): Promise<EntryArray<V>> {
            return this.executeInWorker("getEntries", [keys]);
        }

        getAllEntries(): Promise<EntryArray<V>> {
            return this.executeInWorker("getAllEntries");
        }

        // ...
}

如果其中一个方法名称被重命名,我需要记住手动重命名它的字符串表示,否则我会得到一个运行时错误(假设这段代码已经过彻底测试,否则可能会被遗漏)。

如果我能写下就太好了:

export class LocalDBWorkerController<V> extends LocalDBBase<V> {
        initializeWorkerDB(options: BrowserDBOptions): Promise<void> {
            return this.executeInWorker(nameof(this.initializeWorkerDB), [options]);
        }

        getEntry(key: string): Promise<DBEntry<V>> {
            return this.executeInWorker(nameof(this.getEntry), [key]);
        }

        getEntries(keys: string[]): Promise<EntryArray<V>> {
            return this.executeInWorker(nameof(this.getEntries), [keys]);
        }

        getAllEntries(): Promise<EntryArray<V>> {
            return this.executeInWorker(nameof(this.getAllEntries));
        }

        // ...
}

感谢您重新打开此问题,因为我真的很想看到 nameof 运算符。

nameof 运算符将是一个编译时功能,因此不会影响运行时的性能,但我们可以保存属性名称的重构并防止“魔术字符串”。

讨论了一段时间,我们认为有一个更好的建议可以解决这个用例。

#1295 中描述的这个一般问题空间的解决方案看起来更适合 TypeScript。 如果该提议得到正确实施,您仍然会获得重构和验证的好处,因为字符串将根据上下文键入并标识为属性名称(因此重命名/查找引用/转到 def/等仍然有效)。

1295 还提供了更好的类型检查(因为你不能nameof错误的东西)同时避免了兼容性问题(你可能已经有一个名为nameof的函数!)和“如果 ES7+ 添加这个运营商也是”的问题。

您仍然还可以进行一些良好的表达式级验证,因为您可以编写例如<memberof MyType>"SomeMember" (将被验证!)而不是nameof myTypeInstance.SomeMember 。 这更加有利,因为现在您不需要MyType的实例来获取其属性名称,而这种情况经常发生。

<memberof MyType>"someMember"
  1. 对初学者来说看起来不是很直观或不友好(更抽象,需要了解什么是类型转换、类型修饰符)。
  2. 更长。 添加额外的“噪音”( < >" " )。
  3. 不适用于匿名类型。 例如let x = { prop1: 123 }; let s = nameof(x.prop1) 。 除非使用了一些冗长的语法,例如<memberof typeof x>"prop1"
  4. 不建议嵌套的自然扩展。 例如像nameof(myTypeInstance.someMember.nestedMember)
  5. 不允许编辑器自动完成。
  6. 不为重命名操作本身提供目标(例如单击它并按 F2)。
  7. 信息较少的错误消息:例如"someMember" is not assignable to... "member1" | "member2" | ..而不是更准确的'someMember' is not a member of ...
  8. 不提供排除私有成员或受保护成员的方法(除非提供了publicMemberofpublicOrProtectedMembersOf内容,但这似乎有点过于冗长)。 我认为这是非常重要的功能 - 这是程序员需要的那种安全性, nameof旨在提供。

我认为#1295 很有趣,但似乎最多是互补的(它们可以很好地协同工作)? 这似乎是一个奇怪的决定(尤其是使用“拒绝”类似判决的标签,我觉得这里不合适 - 总的来说)。 我认为nameof是一个很好的语法和概念。 没有必要仅仅因为它可能会干扰未来的 EcmaScript 语法而过度“合理化”或试图证明不实施它的决定是正确的(我相信这似乎是这里的主要关注点)。

回复:第 1-4 点在大多数情况下,您只需编写"someMember"就可以了。 当目标没有提供memberof类型时,我只会使用<...>" "语法,这种情况很少见。

第5点是正确的。

第 6 点不正确; 你可以在那个位置重命名。

当目标是memberof时,第 7 点是不正确的——我认为我们可以在那里提供一个很好的错误消息。

第 8 点,我同意我们需要弄清楚这一点。

尤其是带有“拒绝”的类似判决的标签,我觉得这里不合适 - 总的来说

我不确定替代方案是什么?

class Example {
   a: number
   protected b: string: 
   private c: boolean;
}

function getProp(obj: Example, prop: memberof Example) {
  //..
}

对于 1-4(相对于 8),与nameof的预期行为相比,这里有一个限制。 限制在于,使用nameof ,根据调用的上下文,对于将哪些成员包含为有效成员会有不同的期望。 如果从类外部引用, memberof应该只包含"a" ,从派生类中它会是"a" | "b"而从内部它会是"a" | "b" | "c" 。 我认为没有任何方法可以在这里优雅地“建模”,因为类型通常不是由上下文决定的(尽管一切皆有可能?我想,但我还没有时间真正考虑后果)。

对于 6. 通过字符串重命名会显得有点奇怪。 我想它可以完成,但由于它不在可见的上下文中(例如someInstance.member ),它可能无法防止人为错误。


Edge 平台状态页面使用以下术语:

  • 支持的
  • 预览版
  • 开发中
  • 在考虑中
  • 已弃用
  • 目前没有计划

基于此:

  • 我认为“计划外”(也在 VS Code GitHub 中用作里程碑)将是“拒绝”的一个很好的替代(并且更友好)术语:如果问题已解决,则意味着它“可能永远不会发生”,如果打开,'也许有一天,谁知道'。
  • “正在考虑”可以代替“讨论中”(这与“讨论”标签有些混淆)。
  • “计划中”和“开发中”可能会取代“已承诺”。
  • “重复”有时用于推荐不同方法的建议。 这些并不是真正的“重复”,“合并”之类的东西可能会更好(可能有更好的术语,但我现在想不出任何东西,需要更多时间)。 _[编辑:也许像“融合”这样的东西? 'Merged' 可能与 Git 'Merge']_ 混淆。
  • 也可能是“太复杂”->“不值得”。

我认为其中一些替代方案也更好地反映了这样一个事实,即这并不是一个真正的“社区”导向项目(比如 Node.js)。 像“讨论中”这样的术语似乎是在讨论“社区”而不是设计团队(至少我一开始是这么认为的)。 “拒绝”似乎有点极端(永远不要说永远......)。 对我来说,它感觉就像将功能请求描述为“需求”甚至“试验”一样。 有时它们只是“想法”、想法或需求的基本表达、调查要点等。有时它们可​​能只是作为“阶段”或更好的信息来源。

我愿意为这些工作付出一些个人的努力。 也许当我有一套更完整的我觉得足够好的术语时,我会打开一个元问题。

这是很好的反馈,看起来我们在这方面确实有改进的空间。 我不是“计划外”的忠实粉丝(听起来我们忽略了计划吗?),但我认为你有的清单很好。 如果您确实提出了比我们现在所拥有的更不模棱两可、更友好的内容,我会欢迎新列表。

@瑞安卡瓦诺

是的,我意识到“unplanned”也可以理解为“unscheduled”,所以我需要更多的时间来考虑更好的事情。 无论如何,我有更多的想法(比如可能将“按设计”拆分为“预期行为”,也许还有“设计限制”之类的东西),但这里有点跑题了。 我计划使用FAQ

这是我上面给出填充 ES6 name属性的函数实例

(V8 的 5.0 版在面向 ES6 时不需要这个 polyfill,我相信(在 chrome 50 中.name适用于类方法)虽然在 Node 6.0 中一些功能仍然在一个标志之后):

function polyfillMethodNameProperties(obj: any) {
    while (obj != null && obj !== Function.prototype && obj !== Object.prototype) {
        for (let propName of Object.getOwnPropertyNames(obj)) {
            let member = obj[propName];

            // Note: the latest Edge preview would resolve the name to 'prototype.[name]' 
            // so it might be better to force the polyfill in any case even if the 
            // feature is supported by removing '&& !member.name'
            if (typeof member === "function" && !member.name)
                Object.defineProperty(member, "name", {
                    enumerable: false,
                    configurable: true,
                    writable: false,
                    value: propName
                });
        }

        obj = Object.getPrototypeOf(obj);
    }
}

class LocalDBWorkerController<V> extends LocalDBBase<V> {
        constructor() {
            polyfillMethodNameProperties(this);
        }

        initializeWorkerDB(options: BrowserDBOptions): Promise<void> {
            return this.executeInWorker(this.initializeWorkerDB.name, [options]);
        }

        getEntry(key: string): Promise<DBEntry<V>> {
            return this.executeInWorker(this.getEntry.name, [key]);
        }

        getEntries(keys: string[]): Promise<EntryArray<V>> {
            return this.executeInWorker(this.getEntries.name, [keys]);
        }

        getAllEntries(): Promise<EntryArray<V>> {
            return this.executeInWorker(this.getAllEntries.name);
        }

        // ...
}

我不知道如何以非目标方式应用这个特定的 polyfill 的通用解决方案,尽管它成功地解决了我的特定需求。


可以实现一种不同但更通用的方法来支持具有object类型的成员(但不支持具有原始类型的成员,如stringnumberbooleannullundefined因为它们不能通过引用进行比较),并且还允许重新分配成员。 我在这里使用的方法依赖于 ES6 Map对象(如果可用)来缓存属性列表:

let propertyNameCache: Map<any, string[]>;

function getAllPropertyNames(obj: any): string[] {

    let scanAllPropertyNames = (): string[] => {
        let propertyNames: string[] = [];

        while (obj != null) {
            Array.prototype.push.apply(propertyNames, Object.getOwnPropertyNames(obj));
            obj = Object.getPrototypeOf(obj);
        }

        return propertyNames;
    }

    if (typeof Map === "function") {
        if (propertyNameCache === undefined)
            propertyNameCache = new Map<any, string[]>();

        let names = propertyNameCache.get(obj);

        if (names === undefined) {
            names = scanAllPropertyNames();
            propertyNameCache.set(obj, names);
        }

        return names;
    }
    else {
        return scanAllPropertyNames();
    }
}

function memberNameof(container: any, member: any): string {
    if (container == null || (typeof container !== "function" && typeof container !== "object"))
        throw new TypeError("memberNameof only works with non-null object or function containers");

    if (member == null || (typeof member !== "function" && typeof member !== "object"))
        throw new TypeError("memberNameof only works with non-null object or function values");

    for (let propName of getAllPropertyNames(container))
        if (container[propName] === member)
            return propName;

    throw new Error("A member with the given value was not found in the container object or any of its prototypes");
}

用法示例:

class Base {
    dcba = {};
}

class Example extends Base {
    abcd = {};
}

let example = new Example();
console.log(memberNameof(example, example.abcd)); // prints "abcd"
console.log(memberNameof(example, example.dcba)); // prints "dcba"

_(请注意,这不会检测到新成员的添加,除非使用较慢的未缓存版本 - 它在每次调用时重新扫描整个原型链。但是在 TypeScript 领域中,实际上并不需要将其作为接口或类定义本身必须在编译时知道以提供所需的类型安全级别)_

@RyanCavanaugh关于替代标签建议的工作已完成 95%,我已经涵盖了几乎所有当前标签(这花费了很多时间)——结果证明最困难的标签是Declined标签本身:)所以这可能需要额外的几周甚至几个月的时间。

<memberof MyType>"SomeMember"

这更加有利,因为现在您不需要 MyType 的实例来获取其属性名称

在 C# 中你可以写 var propertyName = nameof(MyType.SomeMember)

似乎 nameof 和 memberof 都有意义。 memberof 用于类型注释,nameof 用于获取对象的成员、变量、函数参数、类、接口、命名空间等的名称...

所以我不明白 - 会不会有任何名称的运营商或没有? 因为票已关闭,但评论不断涌现:)

我在搜索时到达这里:TypeScript CallerMemberName

寻找类似 C# 的 [CallerMemberName] 属性的东西。 与 nameof 不太一样,但在某些情况下用于编写方法,以便调用者甚至不必使用 nameof; 例如,通知某些属性已更改等。

是否有一个单独的问题,或者它是否被认为与此相同?

@DaveEmmerson这是一个不同的话题。 CallerMemberName可以在运行时在 javascript 中找到

此问题是针对变量名称的 _compile-time_ 老化,在运行时不可用。

@styfle如果您阅读该线程,它实际上无法在运行时始终如一地完成。

我说的是像 C# 中的 CallerMemberName 和 nameof 这样的编译时间。 我认为它们具有类似的机制,但我想如果这个问题已经结束,那么就会得出类似的结论。

只是一个想法... :)

我也不完全有这种不情愿

nameof只是一个在语法层面将属性名标识符转化为字符串字面量的宏,为什么要添加它这么大呢?

我用 babel 插件解决了这个问题: https :

我已经在生产站点中使用它几个月了,它的作用就像一个魅力。 终于在github上搞定了。

那个 babel 插件可以安装到 TS 应用程序并一起工作吗?

我的用例是我开发的表单库。 我希望能够使用重构兼容和编译时检查的语法,例如:

// Press F2 to rename properties:
let myObj = { firstName: "John", enableTLAFeature: true, uglyPropName: "" }; 
let myForm = new Form( [ 
  new TextBox  ( myObj, nameof( myObj.firstName ), { required: true, maxLength: 20 } ), 
  new Checkbox ( myObj, nameof( myObj.enableTLAFeature ) ),
  new TextBox  ( myObj, nameof( myObj.uglyPropName ), { label: "Better Label" } )
] );

组件构造函数接受对象、属性名称(作为字符串)和可选的选项对象。

这些表单组件将能够读取和写入属性(使用 obj[propName] 语法),并且如果选项对象中没有提供任何内容(每个组件都包括标签的呈现),还可以自动生成显示标签。 我可以使用辅助函数将属性名称转换为表单标签。 该函数将第一个字母转换为大写,并在每个小写字母之后插入一个空格,当后面跟一个大写字母时,在任何大写字母之前跟一个小写字母。 例如,“firstName”变为“First Name”,“enableTLAFeature”变为“Enable TLA Feature”。 (如果我们需要更好的标签,则可以在选项对象中明确提供。)

注意:对于嵌套对象,我只是在第一个参数中传递嵌套对象,例如myObj.address并且我希望nameof(myObj.address.city) "city"在编译时转换为

以上应该比较容易实现(只需解析最后一个符号并在其周围加上引号)。 但是,一个缺点是我没有属性的类型安全性,所以我不能确保myObj.enableTLAFeature是一个布尔值。 我不确定如何解决这个问题,但我真的更喜欢让调用站点只指定一次属性(用于读取、写入和生成标签)。 如果可以只传递myObj.enableTLAFeature (作为布尔值并且没有 nameof 运算符),然后在组件构造函数中使用callsitenameof运算符(返回“enableTLAFeature”而不返回构造函数参数名称)例如“propName”),那就太棒了,但我不知道这是否可行。

像这样的东西可能非常有用。

nameof<Foo>应该出现(对于所有意图和目的)作为"Foo"这样编译器/发射器可以简单地将它视为一个字符串。

但是跟踪 T 实际是什么的nameof<T>可能对 DI 容器非常有用。 例如:

registerConstant<T>(instance:T){
   this.container[nameof<T>] = instance;
}

那么你可以解决 IOC 的接口问题:

捆绑:
ioc.registerConstant<IFoo>(new Foo());

注射:

constructor(@inject(nameof<IFoo> foo:IFoo){... }

我为此创建了一个小型实验库: https :

它仍在进行中。 如果您有任何建议,请告诉我。

我还需要一个 nameof 宏。 不幸的是,这里提到的替代“更智能的解决方案”似乎不会很快实现。 一个简单的 nameof 比设计得如此复杂以至于永远不会出现或需要数年时间的东西要好。

@MeirionHughes :它如何转换为 javascript?
我不认为它比这个非常简单的替代方案好得多:
ioc.registerConstant(nameof(IFoo), new Foo());

获取属性或函数的名称(讨论的原始主题)与获取类型的名称不同,请考虑:

  • nameof<[]> - 它的名字是什么?
  • nameof<{ coolStuff: MyType & MyOtherType & MyFavoritType | undefined | null | SomethingCompleteltyDifferent<Dictionary<string, Promise<Optional<PleaseStop>>[]>>; }>
  • nameof<string | number> vs nameof<number | string>

让我们一起

  • nameof<string> vs nameof<"string">

这个也很有趣:

  • type N = number; nameof<number | N | number>

等等等等,这里有更多:

function helloEarthings<a, b, c extends a<b>>(one: number, two? = new class { hi = 'how are you?'  }, ...blah: MyMegaControl<typeof someOtherValueIJustMadeUp>[]) {
}
nameof<typeof helloEarhlings>
  • 属性名称的顺序:
nameof<{
    shouldWeGoLexical: 'hm'
    orAlphabetical: 'hm'
}>

@aleksey-bykov 所有这些的答案实际上就是字符串类型; 是否有任何用途是另一回事 - 它在 js 中将打字稿的类型作为有形字符串获取。

@MeirionHughes,您了解写入的类型(在 AST 中)与 TypeScript 读取、内部存储和解释的方式不同,不是吗?

例子:

const myVariable = 'hey';
type A = typeof myVariable
type B = 'hey';

nameof<A> === nameof<B> // <-- ???
nameof<typeof myVariable> === nameof<A> // <-- ???
nameof<'hey'> === nameOf<A> === nameof<typeof myVariable> // <-- ???

@aleksey-bykov:根据定义,nameof 不应与匿名表达式一起使用。 你不能得到一个没有名字的东西的名字。

  • nameof(Array) -> “数组”
  • nameof([]) -> 编译器错误
  • let arr = []; nameof(arr) -> "arr"
  • nameof(string) -> “字符串”
  • nameof(String) -> “字符串”
  • nameof("string") -> 编译器错误
  • type N = number; nameof(N) -> "N"
  • nameof(number | N | number) -> 编译器错误
  • nameof({ coolStuff: MyType }) -> 编译器错误

但是,这应该有效:

  • nameof(Array.length) -> “长度”
  • let arr = []; nameof(arr.length) -> “长度”
  • interface IPerson { firstName: string }; nameof(IPerson.firstName) -> "firstName";
  • let arr: Array<IPerson> nameof(arr[0].firstName) -> “名字”

所有这些都已经在 C# 中定义并在现实生活中进行了测试,并且运行良好。

我更喜欢nameof()表示法而不是nameof<>以避免在 JSX 中发生冲突。 多伦多证券交易所。 这可能是最常见的场景:

<input name={nameof(this.state.firstName)} />

@Liero ,很难

我不清楚能够获得注册类型的姓氏的优点是什么,在 TS 中只有 40% 的情况下,由于各种匿名类型表达式,此类问题甚至有意义

除了:

  • interface A { x: number; }
  • interface B extends A {}
  • type C = B;
  • type D = A;
  • type E = C | D

TS 中的所有类型都是 100% 相同的(感谢结构化类型),我将如何从不同的名称下看到它们(根据您的说法)?

@aleksey-bykov
nameof()不打算用于类型,而是主要用于指针。 我们不关心那个指针的类型,我只想要它的名字。

const a: { what: string;}
const b: { what: number;}
const c: { what: any;}
const d: { what: undefined;}
const e: { what: never;}
const f: { what: {};}
const g: { what: typeof a;}
const h: { what: typeof b | typeof c;}
const i: { what: Whatever | You | Need | Ever;}

nameof(a.what) == 'what';
nameof(b.what) == 'what';
nameof(c.what) == 'what';
nameof(d.what) == 'what';
nameof(e.what) == 'what';
nameof(f.what) == 'what';
nameof(g.what) == 'what';
nameof(h.what) == 'what';
nameof(i.what) == 'what';

// they're all the same, regardless of the type

现在,如果您正在谈论询问nameof()类型,那么我同意@Liero :解决方案是忽略或对任何匿名类型表达式给出通用答案,以及对任何井的真正实际答案定义和命名的类型引用。 我认为没有人会问复杂或模糊类型的名称,我从未想过将其作为此功能的一个案例。

我个人觉得 nameof 的真正价值不在于在运行时获取类型信息,而是在使用字符串文字时提供类型安全。 如上所述,我目前正在使用一个实现 nameof 的 babel 插件; 这是我的一个非常典型的用例(使用 React,因为这是我目前正在使用的内容,所以在我的脑海中是新鲜的,但这个概念可以应用于其他地方):

interface FooBar {
  foo: string;
  bar: string;
}

interface Props {
  value: FooBar;
  onChange(value: FooBar) => void;
}

const FooBarControl = (props: Props) => {
  const handleChange = (event: React.FormEvent, name: string) => {
    const { value } = event.currentTarget as HTMLInputElement;
    const newValue = Object.assign({}, props.value, { [name]: value });
    props.onChange(newValue);
  };

  return (
    <div>
      <input value={props.foo} onChange={e => handleChange(e, nameof(props.foo))}/>
      <input value={props.bar} onChange={e => handleChange(e, nameof(props.bar))}/>
    </div>
  );
};

我可以自由地重命名 foo 或 bar 属性并且没有任何中断,尽管在运行时使用属性名称作为文字字符串更新它们。

@aleksey-bykov:不要期望从 nameof 运算符中获取类型信息。 那将是一个可怕的错误。 也许type N = number; nameof(N)没有真正的用例,但为了清晰和一致性,它非常有意义。 if(true){}也没有真正的用例,但您可以编写它。

@fredgalvao你在谈论属性的名称(不是类型),这是一个完全不同的故事,很有意义,我和你在一起

@Liero如果您不关心类型信息,也许我们应该将其限制为不是类型的属性名称,而是值声明,因为它们的名称属于保留在发出的 JS 中的值,而不是到完全擦除的类型

TS 处理 2 个不同的域:

  • 值域 - 与发出的 JS 相关的一切,因为 JS 中的每一件小事都是一个对象
  • 类型域 - 有助于推断不属于发出的 JS 的代码的一致性的临时事物

现在

  • 获取的名称非常有意义,因为它们在 JS 中是有意义的
  • 获取类型的名称没有多大意义,因为在生成的 JS 中没有任何与它们有关的内容

除了……上课!

类处于暮光之城,因为它们拥有值和类型的属性:它们是值是因为原型对象和构造函数,它们是类型是因为它们管理一组有效操作,使其实例成为其中的一部分

连同所有琐碎的值,如属性、函数、变量,类应该是nameof操作符的对象

@aleksey-bykov:这是讨论的主题,但我看到 nameof 的用例和类的名称:

describe(nameof(Person)) {
   it(nameof(Person.sayHello) + `() should say "Hello!"`, function() {
      expect(new Person().sayHello()).toBe("Hello!");
   });
}

它允许更容易的重构。 虽然附加值相对较小,但我认为没有理由限制它。

仅供参考,这就是 C# 的工作方式:

class A {}
nameof(A)
"A"
using B = A;
nameof(B)
"B"

没问题。

@aleksey-bykov 属性名称是我的用例所需要的,我在上面详细描述过。 并且已经给出了具有相同要求的其他示例。 我们只是希望能够在我们的代码中重命名一些符号(使用重构工具),而不会破坏运行时依赖于这些符号的代码(我们不想将这些符号硬编码为字符串)。

@Liero没有关于是否让类成为nameof的主题的问题,因为类是值,所以让它们存在是很自然的,我认为没有问题

@YipYipX4我听到你说,属性是为nameof ,所以不用担心,它们将是可命名的

获取类型的名称没有多大意义,因为在生成的 JS 中没有任何与它们有关的内容

这完全是重点; 接口根本没有运行时存在......这是一个问题,特别是如果您想基于反射进行控制反转。

看看 inversify 如何解决问题

基本上,如果您有interface IFoo您必须在某处定义字符串“IFoo”,以便在使用 javascript 时获得实际值。 能够拥有接口名称(通过诸如 nameof 之类的东西发出对于确保折射不会破坏这些符号很有用。

因此,鉴于 inversify 示例,您可以执行以下操作:

public constructor(
  @inject(nameof(Katana)) katana: Katana,
  @inject(nameof(Shuriken)) shuriken: Shuriken
)

@MeirionHughes接口不应该有运行时存在,接口只是给定名称的类型,类型都是虚幻的实体,其唯一目的是协助类型检查

现在,你希望你能做的是通过赋予他们额外的责任来重新调整他们的用途,即成为团结在一起的纽带

  • 对某个对象的运行时期望和
  • 通过提供预期对象来满足此预期的一段代码

TS 中的接口不应该按照设计那样做,这不是他们所做的,他们在运行时有 0 个业务

可能让您感到困惑的是名称inteface和您对运行时行为有某些期望的 C# 背景,同样,C# 和 TS 是非常不同的,尽管共享一些语法和关键字

回到您的注入框架,如果使用纯字符串将实现与依赖它的位置链接起来,它也能正常工作:

@injectable('one-thing')
class A {}
@injectable('another-thing')
class B {}
class C {
   constructor(
     @inject('one-thing') katana: Katana,
     @inject('another-thing') shuriken: Shuriken
   ) {}
}

@aleksey-bykov这里是 interfaces 的用例

当然有人可以将所有这些放在常量中,但是如果有人想将 nameof 与接口一起使用以保持其常量字符串值一致,我认为没有充分的理由不允许他们这样做。

@dsherret

我已经看过了,谢谢,使用 TS 接口的名称不会解决任何问题,请考虑:

@injectable() 
class Broom extends Broom { sweep(): void {} }

@injectable()
class Katana extends Katana { cut(): void {} }

class Ninja{
    constructor(
        @inject(nameof(Broom)) katana: Katana // <-- did i get my sword yet?
    ) { }
}

问题:

  • nameof做什么来阻止这样的坏事发生?
  • 如果不能,有什么比使用普通字符串"broom"代替nameof(Broom)?更好

@aleksey-bykov 它会阻止你做“扫帚”而不是“扫帚”;)

严肃地说,我同意它没有为常量提供任何额外的保护(如 inversify 示例所示)。 接口的 Nameof 只是通过将字符串更新为接口名称来提供一种让代码更易于维护的方法。 这真的是唯一的原因。

@dsherret在我说这不值得麻烦之前,让我们更进一步,所以nameof是一个可以接受接口的新功能,在我的 ORM 框架中,我有一个接口interface IColumn<TValue> { } ,现在我需要做nameof(Column<number>)它会给我什么? (提示,见我今天的第一条评论)

@aleksey-bykov 我会说应该排除类型参数—— nameof(Column<number>)应该转到"Column" 。 如果有人想要类型 arg 他们可以这样做: nameof(number); 。 这就是我在这里所做的。 如果没有这样做,那么就无法轻松获得类型为 args 的接口的名称……或者语言中需要有一个例外,以允许在没有类型 arg 的情况下编写它(这会变得很讨厌)。

C#:

     nameof(A.B.C.D);  => "D"  // namespace A.B.C.D 
     nameof(A.B.C.D.IX); => "IX"  // interface IX in namespace A.B.C.D
     nameof(variableName); => "variableName";
     nameof(A.B.C.D.IX<int>); => "IX"  // interface IX<T> in namespace A.B.C.D;
     nameof(instance.Name); => "Name" // property Name of object instance

打字稿:

     nameof(A.B.C.D); => "D"  // module A.B.C.D
     nameof(A.B.C.D.IX); => "IX" // interface IX in module A.B.C.D
     nameof(variableName) => "variableName";
     nameof(A.B.C.D.IX<number>); => "IX" // interface IX<T> of module A.B.C.D
     nameof(instance.Name); => "Name" // property name of object instance

nameof 字符串可以在编译时计算,在 Typescript 中也应该是可能的。
这解决了许多在不同 JavaScript 框架中使用字符串名称和类型名称(1 到 1)的场景。 这将使重构变得更加容易(模块名称、控制器名称、angularjs 中的依赖注入等)
为什么不让 nameof 以与 C# 中相同的方式工作?

@aleksey-bykov 我同意你的观点,即nameof()类型变得很容易变得混乱,但我认为我们不需要去那里提供一个令人满意的解决方案/实现,涵盖 99% 的场景。

平常的东西

  • 我们已经知道并同意属性/字段/变量被 100%(或预期)覆盖。
  • 我们可以在类上限制nameof()以获得最直接的树下定义的无类型参数名称( Class<TypeParam<What<Is<Going | On>>>> -> "Class" )。 我的意思是,如果你想让它被识别,只需别名:
type ClassFromHell = Class<TypeParam<What<Is<Going | On<I | DONT | EVEN>>>>>;

开创性的事情

  • 我们可以在仅编译类型的类型表达式(接口、抽象类、内联定义、类型别名、模块定义等)上限制nameof()以类似于我对上述类的建议的简单和限制方式的行为: 只在调用它的地方内联输出字符串。

它会是第一个让它通过 compile void 到 runtime 从 TS 的东西吗? 也许。 但这很糟糕吗? 我不这么认为。

我真的看不到nameof()能够解决这个概念的所有需求,所以我什至不会要求它具有完整的功能。 但是因为那 1% 的问题而保留其他 99% 的案例似乎不是一个好主意。 我们不是在处理 Ecmascript 考虑的东西,所以我们不需要严格遵循草案或规范,因此我们可以接受这个 _faulty_ 版本,这对我来说已经是10^10*awesome

我在这里提倡魔鬼,当你提出一个建议时,你最好确保你考虑了所有的进/出,你需要对所有可能的情况(不仅仅是接口)给出一个明确的答案,无论是默认的编译器错误,你需要证明为什么它应该这样工作,你需要向你的用户解释它,你最好确保他们喜欢它,你需要分配一些资源来开发和维护它,你需要有一个计划如何在此基础上构建新功能或防止它们与其发生冲突

现在当这个想法简单明了并且它的直接好处对每个人都是显而易见的时,那就是一个故事

所以有人会说不吗? 好吧,如果我们谈论的是一种特殊的interface情况,它只能在没有类型参数的情况下工作,而且我们真的不知道如何处理所有其他类型,因为这个概念根本没有意义,更不用说直观,这似乎已经是一个问题......

现在如果我们确定必须解决这个问题,那么我们最好有一些现实生活中的例子来清楚地展示好处

到目前为止,还没有一个很好的例子来说明为什么我们需要nameof作为接口,除了不支持的关于更容易编写代码和维护的声明

我再次同意你说的大部分内容。 我不会建议或投票支持模糊功能。 这就是为什么我希望看到此功能的定义明确、限制明确的简单版本,而不是根本看不到任何版本。

我不明白到目前为止发布的示例如何被视为“不受支持的陈述”。 它们中的大多数是真实世界的场景,特别是 IoC,在涉及许多不同库和框架的许多项目中使用。 能够编写更好的代码并轻松维护它是创建 Typescript 的初衷。 例如,我们没有 HKT,但我们仍然有达到 v2.0 的 TS : wink:。 我再次同意*再次,它不会解决所有问题,但会解决很多问题。

至于接口(不是属性、变量、类是不可能的),我只看到了一个糟糕的例子和一些响亮的陈述

本质上, nameof试图为接口解决的问题是将接口与字符串相关联,以便:

  • 任何时候你有一个接口,你都会得到一个字符串
  • 以及获取具有字符串的类型的相反问题

有一点不便,这可以在今天尽快在普通 TS 中完成:

type Wrapper<Type> = string & { '': [Type] };
function toWrapperFor<Type>(name: string) : Wrapper<Type> { return <any>name; }
const associations = {
    one: toWrapperFor<string>('one'),
    another: toWrapperFor<MyMegaControl>('another')
};

// ... later in code

class MyClass<Type>{
    constructor(private wrapper: Wrapper<Type>) {}
    take(value: Type): void { /*...*/ }
    toString(): string { return this.wrapper; }
}

const oneInstance = new MyClass(associations.one); // MyClass<string>
oneInstance.toString(); // 'one'
const anotherInstance = new MyClass(associations.another); // MyClass<MyMegaControl>
anotherInstance.toString(); // 'another'

@aleksey-bykov 重点是保证字符串与接口名称相同。 这是一个小好处,但仍然是一个好处。 你的例子没有这样做。 我们已经讨论过使用 IoC 的示例,以及在重构接口名称时重构字符串会有多好。

列出一些原因:

  1. 它保证字符串与接口名称相同。 这在很小的程度上有助于整体代码质量。
  2. 它节省了很少的时间,因为在更改接口名称时,您不必也更改字符串……重构工具将为您完成。
  3. 使用nameof有助于显示字符串代表什么,并且因为它显示字符串代表什么,它允许我们使用“转到定义”快速导航到接口定义。 这有助于代码的清晰度。

这些是我目前能想到的唯一原因。 这些不是大声或不受支持的声明。

我会说对于类型应该只允许接口和类型别名类型——没有联合/相交/对象/字符串/等。 类型。 甚至可能不允许使用关键字(如number )。

同样,不要重新发明轮子。 C# 示例:

class A<T> { }
nameof(A<string>)
"A"

当谈到接口时,已经提到了优点和缺点。 不再需要垃圾邮件讨论。 还有@ahejlsberg和其他很棒的人,他们会在一天结束时做出正确的决定。 让我们收集更多的用例

如果您像我一样使用 NoSQL DB 并在字符串指定的字段上执行大量查询,而这些字段在您进入 Stringland 的古怪领域时会失去重构、一致性、错误捕获和自动完成功能,那么 _ nameof _ 是非常需要的。

这个问题,自2014年开始,真正代表了一句话:最好是更好的敌人

--R

AngularJS 示例,配置 DI:

angular.module("news", ["ngRoute"])
    .controller("NewsIndexController", App.News.NewsIndexController) // nameof

和参考服务:

    .config(($routeProvider: ng.route.IRouteProvider) => {
        $routeProvider
            .when("/news", {
                controller: "NewsIndexController", // nameof
                templateUrl: "modules/news/views/index.html",
                controllerAs: "vm",
            })
    })

我认为这里的很多用途都可以使用装饰器来完成,如果它们更普遍适用的话。 如果实现了函数装饰器,那么它们将能够涵盖这个用例。

此线程中描述的许多场景应由https://github.com/Microsoft/TypeScript/pull/11929 中引入的keyof运算符处理

keyof绝对棒极了! 但是仍然需要一些名称属性,当数据绑定时: <input name={nameof(this.state.firstName)} />

是的,很高兴看到这方面的一些成果。 但是keyof的用例是不同的。 您仍然无法引用以下名称:

  • 提到了@Liero等属性和成员
  • 输入名称,见评论
  • 参数

@mhegazy

keyof很好,但还不够。 nameof在例如声明 Aurelia 中计算属性的依赖关系时仍然_extremely_有用:

@computedFrom(nameof this.foo.bar)
// should compile to: @computedFrom("this.foo.bar")

或者在无效函数参数的错误消息中:

throw new Error(`The value of ${nameof options.foo} must be positive`)
// should compile to: throw new Error(`The value of ${"options.foo"} must be positive`)

这将最终使重构安全。 它们目前维护起来非常痛苦,当你把它们搞砸时,它会悄悄地发生——你只会遇到极难调试的性能问题,以及误导性的错误消息。

我看到上面的一些帖子讨论了这个的不同变体以及对泛型等的关注。在我看来,这应该尽可能简单 - 只给我_确切地_我写的字符串。 所以nameof Foo<Bar>应该完全编译为 - "Foo<Bar>" 。 如果不需要泛型,则可以稍后使用正则表达式将其删除。 我真的不明白为什么这必须如此复杂。

或者,如果这是不可接受的,只需去掉该死的泛型,或者不支持泛型类型——请_请_重新考虑这一点。 这是一个非常有用的功能,非常需要。

@thomas-darling 如果它类似于 C# 定义, nameof foo.bar将是"foo" ,而不是"foo.bar"

无论如何,你可以实现类型安全,不合格名称的computedFrom装饰器,使用

// computed-from.ts
export function computedFrom<K1 extends string, K2 extends string, K3 extends string>
  (prop1: K1, prop2: K2, prop3: K3): 
    (target: {[P in K1 | K2 | K3]}, key: string | number | symbol) => void;

export function computedFrom<K1 extends string, K2 extends string>
  (prop1: K1, prop2: K2): (target: {[P in K1 | K2]}, key: string | number | symbol) => void;

  export function computedFrom<K extends string>
    (prop: K): (target: {[P in K]}, key: string | number | symbol) => void;

然后像这样消费

// welcome.ts
import { computedFrom } from './computed-from';

export class Welcome {
  firstName: string;

  lastName: string;

  @computedFrom('firstName', 'lastName') get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

"foo.bar"这样的限定名称也是可能的。

@aluanhaddad

像“foo.bar”这样的限定名称也是可能的。

这是我不知何故错过的东西,要详细说明吗?

我认为有可能重载标记的模板字符串声明,但经过进一步思考,我认为它不会起作用。 对于任何混淆,我深表歉意。

是的,所以不是引入 nameof 你建议扩展所有库,如 immutablejs 等?

@aluanhaddad同样,您提供的这些示例并没有解决在字符串中包含代码引用的“问题”,这些代码引用几乎无法自动或轻松地重构。 在我的 PoV 中nameof的主要目标是使这些字符串可重构和可搜索。

@fredgalvao这可能会改变 #11997

@RyanCavanaugh如果新的keyof功能确实解决了nameof这个用例,您能否提供一个示例...最好使用TypeScript Playground来证明它有效? 谢谢!

从头开始 - 我自己尝试过,它几乎完美地工作。

interface User {
    name: string;
    age: number;
}

var str1: keyof User;
var str2: keyof User;
var str3: keyof User;

str1 = "name"; // successful compile
console.log(str1);

str2 = "Age"; // compiler error due to typo
console.log(str2);

str3 = "age"; // successful compile
console.log(str3);

游乐场源

当我尝试在 vs 代码中使用Rename Symbol (F2)时,它不会重命名我的字符串,这是我对nameof(mySymbol)功能所期望的。

此外,我没有看到使用函数参数处理用例的方法:

function log(name: string, age: number) {
    console.log(nameof(age), ' is ' , age);
}

所以keyof使我们达到了我认为的 80%,但nameof仍然更好。

更新

我在此处添加了 aluanhaddad 的示例以涵盖这两个用例: Playground Source

函数参数名称是一种更难驯服的野兽,因为在运行时该名称可能完全是别的东西,因为它是被 99% 的后处理器缩小/丑化的确定案例之一。

我的用例是套接字。

使用 socket.io,套接字发射器/接收器使用字符串链接。

所以目前我正在写这个:
(其中showX()是客户端和服务器端的接口实现)

public showLobby(): void {
    this.server.emit("showLobby");
 }

我想写的:

public showLobby(): void {
    this.server.emit(nameof(this.showLobby));
 }

(我知道我可以写this.showLobby.name ,但是有编译时字符串会很好)

第二个实现将允许我重构我的接口,并且客户端/服务器魔术字符串也会自动更改。 据我所知,keyof 是不可能的。

当我尝试在 vs 代码中使用重命名符号 (F2) 时,它不会重命名我的字符串,这是我对 nameof(mySymbol) 功能所期望的。

确实,但至少您会收到一个编译时错误,让您知道您必须更新这些使用站点。 如果不是最方便的话,这会使重构操作安全。

对参数也可以这样做,因此日志示例与其他示例一样有效,并且还演示了此功能的真正强大方面之一

function log<K extends keyof User>(name: K, value: User[K]) {
  console.log(name, ' is ', value);
}

const user: User = {
  name: 'Jill',
  age: 50
};

log(str1, user[str1]); // OK
log(str3, user[str3]); // OK
log(str3, user[str1]); // Error
log('age', user.name); // Error

深度相关的 f 边界。

@aluanhaddad我认为您的log示例与您说明的完全不同。 我的理解是以下内容完全可以:

function log<K extends keyof User>(name: K, value: User[K]) { }

const user: User = {
  firstName: 'John',
  lastName: 'Doe'
};

log('firstName', user.lastName);  // OK, unfortunately

这是因为K被推断为'firstName' ,因此, U[K]被推断为stringlastName满足。
你的例子很好,只是因为每个属性都有不同的类型。

确实,但至少您会收到一个编译时错误,让您知道您必须更新这些使用站点。 如果不是最方便的话,这会使重构操作安全。

大多数时候,但并非总是如此。 您可以进行导致名称冲突的更改,但仍然可以编译。 就像重命名一个方法以引入另一个与旧方法同名的方法。

对我来说更重要的工具是_查找所有引用_,它根本不起作用。 它的替代品是一个纯文本 _Find_,但是 (a) 你需要养成不再使用 _Find references_ 的习惯,这是一种耻辱和 (b) 对于常用名称,_Find_ 非常嘈杂并且会产生大量的误报。

去投票#11997!

@RyanCavanaugh :例如, 必不可少的

@RyanCavanaugh :也许是时候重新开放了? 我们已经收集了足够多的 keyof 没有帮助的用例。 谢谢

我现在做了一个 gulp 任务来完成它。 只需在打字稿编译器之前运行它。 如果有人感兴趣:

可能不适合生产。

var replace = require('gulp-string-replace');

gulp.task("nameof", function () {
    var regexReplace = function (match) {
        // nameof(foo.bar) => foo.bar
        var propName = match.slice(7, -1).trim();
        // foo.bar => bar (also works if no . present)
        var endElement = propName.split(/[.]+/).pop();
        return '"' + endElement + '"';
    };

    return gulp.src("./src/**/*.ts")
        .pipe(replace(/nameof\s*\([^\)]*\)/, regexReplace))
        .pipe(gulp.dest("./nameof"));
});

@CoenraadS如果您使用ts-nameof感兴趣,因为大部分工作已经在那里完成(我在本主题前面提到过)。 我在打字稿中使用 nameof 已经有一段时间了。

无法理解,如果有人做了 ts-nameof, gulp 任务 - 为什么对 ts 团队来说这是一个问题!

@vytautas-pranskunas-基于这个线程中关于keyof的评论,我这样做是为了强制正确使用Immutable中的密钥:

export interface IAppState {
    isConnectedToServer: boolean
}

const checkKey = <T>(key: keyof T) => key;

export const reducer: Reducer<IAppState> = (state = initialState, action: KnownAction) => {
    switch (action.type){
        case 'CONNECTED_TO_SERVER':
            return state.set(checkKey<IAppState>('isConnectedToServer'), true)
        case 'DISCONNECTED_FROM_SERVER':
            return state.set(checkKey<IAppState>('isConnectedToServer12345'), false) //Compile time error :)

可以说,它比 nameof 更冗长,并且在重构时您得不到任何帮助,但至少您可以进行编译时检查。

@paulinfrancis是的,可以这样做。 但主要问题仍未得到解答:为什么社区应该做各种帮助库和黑客来获得如此明显的行为,为什么 ts 团队拒绝实施这一点?

然后是重构支持,我们也不会通过keyof 。 我理解并尊重您不愿意引入新关键字,但这是非常需要的基本语言功能,并且具有明确且无可争议的用例 - 缺乏它会导致每天的严重痛苦和令人讨厌的错误。 重新开放还需要什么?

每隔 3-4 个月,我就会回到这个线程,结果再次对不包含nameof()运算符的顽固感到失望。 对于很多人来说,这确实是一个主要的麻烦,并且不将其包含在其他一些用例中只是......令人难以置信。

每个人似乎都同意它应该或多或少地像 C# 那样工作。

我认为我们应该组织一次抗议——开车到雷德蒙德和纠察 MS 总部......因为我真的没有想法。

没有足够的人在乎,创造关于已知问题的新问题效果最好

我没有检查转换 API,但我想在转换中实现nameof会相对容易。 转换甚至可能随编译器一起提供,但不是它的官方部分。 从这个角度来看,团队不过早实施它是一个非常明智的举措。

@gcnew什么容易? nameof([put valid identifier here]) -> 该标识符的最后一部分作为字符串。 我的意思是这是一个美化的宏。 事实上,在工作中,我们正要添加一个函数nameof(thing:any) ,该函数将被替换为@frodegil在上面使用正则表达式或其他编译后所建议的内容。 我确信有一些边缘情况在技术上可能需要更多的思考,但这基本上涵盖了这个线程中表达的所有用例。 将nameof()添加到一个魔法字符串和缺乏反射keyof() 。 棕榈。 脸。

@gleno keyof是一个非常有用的功能。 事实上,它解决的问题与人们想要的nameof是另一回事。

从我的角度来看, nameof不应该被实现,因为它清除了一个有效的标识符并添加了非标准的表达式级别语法。 当 Transforms API 被合并时,自己编写一个转换或使用最好的社区应该是相当简单的。

@gcnew我的理解是转换来自现有语法 -> 现有语法, nameof是新语法所以它错过了,我希望我错了

@aleksey-bykov 是的,我也这么认为。 但是,如果您有一个函数声明,例如:

declare function nameof(x: any): string;

以及一个用字符串化的参数替换所有全局nameof调用的转换,它应该可以工作。

编辑:在需要字符串文字的情况下,这样的解决方案是不够的。 尽管如此,这是一个很好的第一步,可用的编译器 API 越多,改进的地方就越大。

@gcnew :如果这将成为 nameof 方案的官方解决方案,那么请将其写入文档,例如此处: https ://www.typescriptlang.org/docs/handbook/gulp.html

@gcnew如果你看看 Roslyn,扩展点是诊断、修复和重构,它们在开发过程中提供帮助,并且没有使用它们中的任何一个的风险。 我想做的最后一件事是添加随机包,这些包修改了构建管道的核心、编译和支持这些具有不同hacks 的不存在的语言结构。
然后最终出现诸如“我们无法升级到 TypeScript 2.x,因为“nameof 包”0.1.2 尚不支持之类的问题,因此 1) 我们将不得不等待或 2) 从我们的代码并松散它;或者 3) 自己重新实现它并在几个月内永远维护它”。

@Peter-Juhasz 谁让你从 npm 下载它? 自己做,成为您自己的 24/7 客户服务

转换api与这些情况关系不大

@Peter-Juhasz 我同情你,我同意nameof会很有用并增加安全性。 然而,TypeScript 是关于建模 ECMAScript,而不是组成一种新的更好的语言。 诸如keyof类的功能纯粹是类型级别的,这就是它们被实现的原因。 即使对他们来说,分配关键字也存在阻力。 相比之下, nameof是严格的表达式级别构造。 我认为团队永远不会被说服去实现它,除非它在 ​​ECMAScript 规范中。 我自己不喜欢清除nameof标识符的想法。

这给我们留下了两个选择:

  • 提出一个 ECMAScript 提案并希望它得到关注
  • 使用公共 Transfroms API 并有一个很好的近似

第一个选项需要数年时间(如果它获得批准),第二个应该足够好。 我没有使用 TS API 的经验,但我希望它们稳定且向后兼容,因此 _nameof package_ 也应该稳定。

我不同意 Marin,nameof 不是语言功能,它只启用
类型检查(类似于 keyof),一旦编译后 nameof 消失,它的
参数作为字符串保留。
如果 keyof 没有,我不明白为什么这需要成为 ecmascript 的一部分。

2017 年 2 月 14 日 19:19,“Marin Marinov”通知@ github.com 写道:

@Peter-Juhasz https://github.com/Peter-Juhasz我对你有感觉,我也有感觉
同意 nameof 会很有用并增加安全性。 然而 TypeScript 是
关于建模 ECMAScript,而不是组成一种新的更好的语言。 特征
诸如 keyof 纯粹是类型级别,这就是为什么他们得到
实施的。 即使对他们来说,团队也有阻力
分配一个关键字。 相比之下, nameof 严格来说是一个表达式级别
构造。 我认为团队永远不会被说服实施它,
除非它在 ​​ECMAScript 规范中。 我自己不喜欢这个想法
清除标识符的名称。

这给我们留下了两个选择:

  • 提出一个 ECMAScript 提案并希望它得到关注
  • 使用公共 Transfroms API 并有一个很好的近似

第一个选项需要数年时间(如果获得批准),第二个选项
应该够体面了。 我没有使用 TS API 的经验,但是
我希望它们稳定且向后兼容,因此名称包也应该是稳定的。


您收到此消息是因为您订阅了此线程。
直接回复本邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1579#issuecomment-279790055
或静音线程
https://github.com/notifications/unsubscribe-auth/ABOZ9c-85P3x5AMakVDuxlNkH56me3POks5rcfAYgaJpZM4DNVgi
.

至少对于接口来说,编译后会很糟糕(不产生代码,对 JS 标准没有影响)
我们可以拥有公开所有元数据的辅助函数。
这是相当微不足道的(我目前正在用 bash 脚本来做),并且
不仅有 nameOf,还有实现/扩展信息、字段列表、字段名称及其数量(签名),这将非常有用。

如果有兴趣,请联系我,我们可以写一份提案,最坏的情况是写一个预处理器。
(时间和动机允许)

--R

@gcnew
1. nameof 永远不会出现在 ES 规范中。 我非常有信心这么说,因为 nameof 在 EcmaScript 中没有多大意义。 原因如下:由于 javascript 中的动态输入, nameof(myObject.MyProperty)"myProperty"完全相同。 两者基本上都是“魔术弦”。 没有理由在 javascript 中使用冗长的 nameof 语法。 或者您是否看到在 javascript 中使用 nameof 的任何优势?

正如@RyanCavanaugh提到的,只有标准化的 ES 特性或永远不会出现在 ES 规范中的特性才可以考虑用于打字稿,以避免像模块一样发生混淆。 这是第二种情况。

  1. TypeScript 的最基本目的是提供静态类型并允许更好的重构工具。 在 DataBinding 场景中经常需要属性名称,并且以类型安全的方式这样做正是您对 TypeScript 的期望

我是一个务实的程序员,如果可以做到,我不在乎如何做到。 考虑 ES 兼容性、易用性、性能或您认为相关的任何内容,选择您认为最适合产品的任何内容。 如果您决定使用 Transfroms API,就这样吧,但看在上帝的份上,这个问题值得解决。

@Liero您永远无法知道未来版本中是否会使用有效的标识符。 ECMA 可能不会直接使用它,也可能不会出于此处讨论的目的使用它,但它可能会在浏览器 API 中使用。 考虑一个全局函数nameof(node: Node): string | undefined ,它读取 DOM 节点的name属性。 这样的功能会被标准化吗? 可能不是_这个_,但可能是关于证书或Symbol的。 重点是nameof不能作为关键字借用。 已经有代码将它用于局部变量名称,未来的 API 也可能会使用它。 这将是一个不幸的重大更改,同时与 ECMAScript 规范背道而驰。

解决方案是将nameof提升到类型级别,或者将nameof为可选的前/后处理步骤,这不是官方语言的一部分。

类型级别的、完全可擦除的nameof可能是:

declare const options: {
    easing?: string,
    duration: number
};

if (!options.easing) {
    throw new Error(<nameof options.easing> 'easing');
}

等级

nameof(path.to.identifier)永远不会在编译过程中幸存下来。在最终编译的代码中看到nameof绝对不是每个人都想要的功能。 我们希望它消失,并留下字符串"identifier" ,这使得nameof作为编译/类型级别的实现正是它所需要的。 之间的唯一区别:

type AliasToUnion = A | B;

nameof(object.property)

结果是:一个给出一个空字符串,另一个给出一个非空字符串。 两者都是类型级别的结构,一旦编译完成,它们的外壳就会消失。

碰撞

如果现在的论点是我们不能冒打字稿的nameof和未来 ecma 的nameof之间的关键字冲突的风险,那么我会说同样的论点几乎适用于打字稿没有的所有内容在与 es8 草稿的交叉点中,这基本上是很多类型的东西,因此使这个论点非常虚假。

欲望

即使我们不走关键字的方式,这对我来说也差不多总结了为什么我仍然认为这是最好的打字稿工作(无论是转换、编译级别函数、类型级别结构、ecma 建议等) :

TypeScript 的最基本目的是提供静态类型并允许更好的重构工具。 在 DataBinding 场景中经常需要属性名称,并且以类型安全的方式这样做正是您对 TypeScript 的期望。

建议

<nameof options.easing> 'easing'

看起来它几乎做到了,但无论如何我们都必须手动重构那个不那么神奇的字符串,唯一的区别是我们可能会让编译器告诉我们它的值不代表标识符的实际名称. 这是一个refactor->compile->see_error->go_back_and_properly_refactor流而不是refactor_properly 。 有了这个,我宁愿坚持一个更完整的版本而不是这个提议。 现在, @gcnew ,如果我们能让<>成为一个不需要转换值的结构,那就太棒了(我认为我们不能自动取款机)。

伙计们,肯定需要nameof ,这意味着虽然有 90% 的解决方法,只需要一些额外的输入:

type NamesOf<T> = { [P in keyof T]: P }
interface Data {
    name: string;
    value: number;
}
const propertyNames: NamesOf<Data> = {
    name: 'name,' // <-- not a magic string anymore, CAN ONLY BE `name`
    value: 'value' // <-- not a magic string anymore, CAN ONLY BE `value`
}

image

@gcnew

考虑一个全局函数 nameof(node: Node): string | 不明确的

在现有的 javascript 文件中使用 - 完全没有问题。 在打字稿中,可以通过以下方式避免冲突:

var _nameof = window.nameof; //problem solved

顺便说一句,在 javascript 中,该函数可能会被称为 nameOf

@fredgalvao :您的建议: <nameof options.easing>已经与 JSX 冲突,与 TSX 冲突

@Liero这不是我的建议,我只是在评论@gcnew的建议。

好的,如果有 typeside nameof 运算符怎么办?

看起来像这样的东西:

const obj = {x:1};
function abc(thing:nameof(obj.x)){ }

哪个会表现得完全一样

function abc(thing:"x"){ }

它不如nameof(accessor)->string干净,但我认为它可能非常有用。

我认为nameOf作为编译时关键字,并且看不到它的任何运行时或全局使用(永远不要说永远,但它不太可能,IMO - 称之为TSNameOf ,它是更不可能)

我们应该害怕碰撞吗? 如果,或者什么时候,EcmaScript 成为一种在未来需要nameOf关键字的语言,我认为它已经发展到目前为止,并且采用了 TS 的所有特性,这样我们就不再需要 TS 了。

TS 中的nameOf行为方式应该与 C# 中的nameOf完全相同

C# (v5) 规范:

表达式的名称是一个常量。 在所有情况下, nameof(...) 在编译时评估以生成字符串。 它的参数不会在运行时评估,并且被认为是无法访问的代码(但是它不会发出“无法访问的代码”警告)。

到目前为止,我已经花了太多时间在我的 TS/Angular 代码中“重构”字符串文字。 毕竟,Typescript 的主要目的是成为强类型的替代品,对吧?

快点,在 TS 过时之前包含编译时nameOf关键字:)

像下面这样的自定义nameOf函数怎么样?

declare const options: {
    easing: string,
    duration: number,
};

function nameOf<T, K extends keyof T>(_: T, key: K) {
    return key;
}

nameOf(options, 'easing'); // 'easing'
nameOf({ options }, 'options'); // options

PS: keyof重构由https://github.com/Microsoft/TypeScript/issues/11997跟踪

我有一个非常简单的nameof实现,使用转换 api (#13940) 实现,我今天早上很快做了这个(参见 ts-nameof 的这个分支——特别是这个文件并在这个文件中使用)。

如果我们在 tsconfig.json 中有一种方法可以在编译时指定外部转换前后的路径,我觉得这比直接在编译器中实现更强大。

编辑:该现在支持 babel 和 typescript 编译器。

对于我的两分钱,我很想看到 nameof 纯粹是为了摆脱与 Inversify 绑定的字符串。

我也在研究这个。 我希望这将是现在实施的一个明显功能。 我希望从自定义事件设置 (bla bla bla ;)) 中的方法名称中获取事件字符串名称,除此之外(例如存储命名空间和类型元数据等)。 希望我们不需要再讨论 2 年。

我也很想看到。 我正在处理一些 ASP.NET AJAX 遗留代码,并且必须处理以下内容:

DerivedClass.registerClass( "DerivedClass", BaseClass );

这样做真的很好:

DerivedClass.registerClass( nameof( DerivedClass ), BaseClass );

目前,重命名DerivedClass会破坏代码。

nameof() 很适合使用 Immutable.js Record。

这段代码

class MyRecord extends Record({value: ""}) {
  readonly value: string;

  withValue(newValue: string) {
    return this.set("value", newValue) as this;
  }
}

可以重写为

class MyRecord extends Record({value: ""}) {
  readonly value: string;

  withValue(newValue: string) {
    return this.set(nameof(this.value), newValue) as this;
  }
}

,重命名是安全的。

类或变量名称的潜在解决方法:

function nameof<T, P extends keyof T>(descriptor: {[P in keyof T]?: T[P]; }): P
{
    for(var key in descriptor) {
        if(descriptor.hasOwnProperty(key)) {
            return key as P;
        }
    }
}

class Test { }
var test= ""

var x = nameof({ Test })  // x: "Test" = "Test"
var y = nameof({ test })  // y: "test" = "test"

@jankaspar是否可以将 Test.foo 与您的示例一起使用,其中 Test - class, foo - class 属性?

@vytautas-pranskunas-
不,为此,您使用映射类型会更好。 像这样的东西:

class Test {
    foo: string
}

function propertyName<T>(name: keyof T){ 
    return name;
}
propertyName<Test>("foo") 
propertyName<Test>("bar")  // error

请注意,它并不完美。 我曾尝试使用类似的东西,但失败了,因为接口太复杂了,编译器没有为keyof T生成"field1" | "field2" ,而是生成了类似any ,任何东西都可以编译。

谢谢!
Spark by Readdle

关于如何使用类和接口来做到这一点是否有任何共识? 我明白我怎么能只keyof但基本上我想使用 nameof(type) 这样它会让日志记录更容易,例如LogManager.getLogger(nameof(MyClass))

@niemyjski这在标准 JavaScript 中可用。

class MyClass {
   hello(n) { return 'hello ' + n; }
}
console.log(MyClass.name);

函数名

@styfle如果代码被缩小就会失败,这就是为什么这要求编译时操作符在输出中生成一个字符串并且不会受到缩小的影响

@dpogue大多数压缩器都提供了禁用名称修改的选项(全局,或用于带注释的函数/类); 那可能满足?

nameof(MyClass) != MyClass.name (在缩小的情况下)也可能令某些人感到惊讶

另外,#8 和 #16037 可能有一些相关的讨论需要关注

是的,我们需要一些可靠的东西......

谢谢
——布莱克·涅米斯基

2017 年 7 月 19 日,星期三,上​​午 11:47,Ian MacLeod通知@github.com
写道:

@dpogue https://github.com/dpogue大多数压缩器都提供了一个选项
禁用名称修改(全局,或用于带注释的函数/类); 那
可能满足?

另外,#8 https://github.com/Microsoft/TypeScript/issues/8和 #16037
https://github.com/Microsoft/TypeScript/issues/16037可能有一些
相关讨论要关注


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/Microsoft/TypeScript/issues/1579#issuecomment-316447179
或静音线程
https://github.com/notifications/unsubscribe-auth/AA-So1aBKzLHIsS4-wYSeZmtNOf55B9Rks5sPjMYgaJpZM4DNVgi
.

@nevir@dpogue在您尝试通过接口而不是通过 DI 解析具体类型的情况下,此解决方案可能不起作用 - 即无法在运行时解决接口名称可用性问题。

我最近发现需要这样的东西,因为keyof不允许合格的标识符。

由于我在示例中使用的 API 现在支持使用点状路径字符串的限定名称,因此我用来提高其类型安全性的增强功能:

export function computedFrom<K1 extends string, K2 extends string>
  (prop1: K1, prop2: K2): (target: {[P in K1 | K2]}, key: string | number | symbol) => void;

  export function computedFrom<K extends string>
    (prop: K): (target: {[P in K]}, key: string | number | symbol) => void;

不能再表达它的能力。

为什么过了这么久,TS 仍然没有机制(例如nameof )来消除MAGIC STRINGS

我不明白为什么这个线程有这么多的乞求、理由和用例。 这很简单 - 这应该是重中之重。 是JS最可怕的地方,TS还没有解决。

+1

这将是一个有用的功能

那么人们的故事是什么,这是正在发生的吗?

正如@jakkaj之前提到的,这将是一个与inversify一起使用的非常棒的功能。

以下是您_当前_在使用接口时必须执行的操作的示例:

interface Weapon {
    hit(): string;
}

// Define an object that contains all of the type 
// symbols so that we only use magic strings once.
let TYPES = {
    Weapon: Symbol("Weapon")  // Magic String! Oh No!
};

@injectable()
class Ninja {
    constructor(@inject(TYPES.Weapon) weapon: Weapon) { }
}

使用nameof运算符,您至少可以摆脱魔法字符串,但也可以摆脱包含所有类型符号的对象:

interface Weapon {
    hit(): string;
}

@injectable()
class Ninja {
    constructor(@inject(Symbol(nameof(Weapon))) weapon: Weapon) { }
}

我相信字符串也可用于声明注入的内容,因此您甚至可以将构造函数更改为:

@injectable()
class Ninja {
    constructor(@inject(nameof(Weapon)) weapon: Weapon) { }
}

@reduckted

以下是使用接口时当前必须执行的操作的示例:

interface Weapon {
    hit(): string;
}

// Define an object that contains all of the type 
// symbols so that we only use magic strings once.
let TYPES = {
    Weapon: Symbol("Weapon")  // Magic String! Oh No!
};

@injectable()
class Ninja {
    constructor(@inject(TYPES.Weapon) weapon: Weapon) { }
}

实际上,那不是魔法字符串。 你也可以写

const types = { // this should be frozen in real code.
  Weapon: Symbol()
};

并且含义是相同的(所有使用代码都必须引用types对象的Weapon属性)。

使用 nameof 运算符,您至少可以摆脱魔法字符串,但您也可以摆脱包含所有类型符号的对象:

interface Weapon {
    hit(): string;
}

@injectable()
class Ninja {
    constructor(@inject(Symbol(nameof(Weapon))) weapon: Weapon) { }
}

这可能不会产生预期的效果,因为每次调用Symbol都会创建一个新的唯一值

Symbol("x") === Symbol("x") // false

我相信字符串也可用于声明注入的内容,因此您甚至可以将构造函数更改为:

@injectable()
class Ninja {
    constructor(@inject(nameof(Weapon)) weapon: Weapon) { }
}

如果提议的功能类似于 C# 的nameof运算符,这将导致@inject("Weapon") ,引入隐藏的全局依赖项和令牌冲突。

就像是

interface Weapon {
    hit(): string;
}
const Weapon = Symbol();
export default Weapon;

似乎更可取,但它仍然不理想,因为。 问题是保持名称同步以维持声明合并,但与实际值无关。

想要nameof原因还有很多,但我认为它不适合 DI。

@aluanhaddad

这可能不会产生预期的效果,因为每次调用 Symbol 时它都会创建一个新的唯一值

啊,是的,确实如此。 Symbol.for会解决这个问题,但正如您指出的那样,如果您只创建一次符号并导出它,则用于符号的名称在某种程度上是无关紧要的。

这将导致@inject("Weapon") ,引入隐藏的全局依赖项和令牌冲突。

是的,存在令牌冲突的可能性,但是在所有接口都有唯一名称的小项目中,它会工作得非常好(而且 InversifyJS 使它非常易于管理,正如我最近发现的那样 😁)。

我可以看到nameof非常适合依赖注入_如果您知道这些限制_。 就像前面有人在本线程中提到的那样,将nameof与缩小代码一起使用时会有一些限制。 例如,您可以使用nameof来获取参数的名称。 在缩小过程中,参数名称发生了变化,但编译器生成的“魔法字符串”保持原来的名称。 也许您想要原始名称,或者您想要修改后的名称。 只要您知道nameof会带来的限制,您就会没事的。

+1

到目前为止,这里有几个更适合我的目的的 hacky 解决方法函数,它们可以通过静态检查获取嵌套 prop 的名称或路径。

function nameOf<T>(obj: T) {
  let name: string | undefined;

  const makeCopy = (obj: any): any => {
    const copy = {};
    for (const key in obj) {
      Object.defineProperty(copy, key, {
        get() {
          name = key;
          const value = obj[key];
          if (value && typeof value === "object") {
            return makeCopy(value);
          }
          return value;
        }
      });
    }
    return copy;
  };

  return (accessor: { (x: T): any }): string | undefined => {
    name = undefined;
    accessor(makeCopy(obj));
    return name;
  };
}

function pathOf<T>(obj: T) {
  let path: string[] = [];

  const makeCopy = (obj: any): any => {
    const copy = {};
    for (const key in obj) {
      Object.defineProperty(copy, key, {
        get() {
          path.push(key);
          const value = obj[key];
          if (value && typeof value === "object") {
            return makeCopy(value);
          }
          return value;
        }
      });
    }
    return copy;
  };

  return (accessor: { (x: T): any }): string[] => {
    path = [];
    accessor(makeCopy(obj));
    return path;
  };
}

用法示例:
screen shot 2018-02-26 at 15 59 00

当它应该在类型级别时,为什么这是表达式级别的运算符?

像这样:

type Meme = {}
type S = nameof Meme // NameOf<Meme> perhaps better?

const wrong: S = "wrong" // Type '"wrong"' is not assignable to type '"Meme"'.
const ok: S = "Meme"

@goodmind为什么要将“Meme”(理论上是字符串)的名称分配给类型? 这是没有意义的。 您指的是 “typeof” 吗?

const Why_does_TypeScript_not_have_this_yet: Explanation = { provided: false, reason: null };
console.log(`${nameof(Why_does_TypeScript_not_have_this_yet)}?`);
console.log(Why_does_TypeScript_not_have_this_yet);

if (!Why_does_TypeScript_not_have_this_yet.provided && (new Date()).getFullYear() >= 2018)
    this.me.sad = true;

我非常怀疑他们是否会实现这一点,因为nameof不在类型命名空间中,也不在任何 ECMAScript 提案中。

不过这不是问题……如果实现了#14419(“自定义转换器的插件支持”),那么我们可以使用我们自己的nameof ,它可能比标准的nameof更强大

例如,如果#14419付诸实施,那么我们将能够指定转换插件(例如nameof实现这里tsconfig.json):

{
  "compilerOptions": {
    "customTransformers": {
      "before": ["node_modules/ts-nameof"]
    }
  }
}

所以总的来说,我认为要求 #14419 比这个问题更有意义,因为它打开了 nameof 和其他转换可以轻松集成到项目中的大门。

老实说,如果直接以“我们不想要这个,抱歉不抱歉”结束,然后沉默和缺乏分类会更好。

ಠ_ಠ

这是一个运行时版本,非常适合我的需要:

/** Returns the name of a namespace or variable reference at runtime. */
function nameof(selector: () => any, fullname = false) {
    var s = '' + selector;
    var m = s.match(/return\s+([A-Z$_.]+)/i)
        || s.match(/.*?(?:=>|function.*?{)\s*([A-Z$_.]+)/i);
    var name = m && m[1] || "";
    return fullname ? name : name.split('.').reverse()[0];
}

如果命名空间是 ABC 并且在“C”范围内:
用法: nameof(()=>A.B.C) //(返回“C”)
用法: nameof(()=>A.B.C, true) //(返回“ABC”)

在重构时也能很好地工作。 我只在脚本第一次加载时使用它来拉取命名空间+类路径,所以它对任务来说效率更高。

+1
添加该功能将非常有帮助。
用例例如:

ngOnChanges(changes: SimpleChanges) {
    let itemPropName: keyof MapComponent<T> = "items";
    if (changes[itemPropName])
      this.setItems();

    let centerCoordsPropName: keyof MapComponent<T> = "centerCoords";
    if (changes[centerCoordsPropName])
      this.goToCoordinates();
  }

@injectable()
忍者类{
构造函数(@inject(nameof(Weapon)) 武器:武器) { }
}

正是我想要的

如果您使用依赖于装饰器的框架,例如 TypeScript IOC、Angular 2+ 或@fluffy-spoon/inverse ,则@rayncc无需在此处使用nameof @fluffy-spoon/inverse

@ffMathy

我不同意, nameof对于注入接口特别有用,接口只存在于名称中,而不是作为实际的 JS 对象。 这就是为什么你不能做@Inject(MyInterface) ,而你必须做@Inject('MyInterface') 。 如果nameof至少可用于接口,我们可以执行@Inject(nameof MyInterface) ,这比字符串(容易出现拼写错误)要干净得多,更重要的是,可以很好地与重构一起使用。

С# 有nameof为什么不在 TypeScript 中添加类似的功能? 这将非常有帮助。 如果将提高代码健壮性。 即我们可以写而不是if (protoPropertyIsSet(msg, "expiration_utc_time"))
if (protoPropertyIsSet(msg, nameof(msg.expiration_utc_time)))

@stasberkov C#完全控制他们的语法,Typescript 必须小心翼翼地围绕 JS 和任何未来的 JS 发展。 除了枚举,类型注释和类型断言是表达式级别语法的主要扩展。 任何新的表达式级别的语法都有与 JS 背道而驰的危险,并使 TS 在某种程度上与 JS 不兼容。 这就是为什么添加nameof不是一个容易的决定。 实施它是微不足道的,但长期影响可能最终会产生影响。

@stasberkov C#完全控制他们的语法,Typescript 必须小心翼翼地围绕 JS 和任何未来的 JS 发展。 除了枚举,类型注释和类型断言是表达式级别语法的主要扩展。 任何新的表达式级别的语法都有与 JS 背道而驰的危险,并使 TS 在某种程度上与 JS 不兼容。 这就是为什么添加nameof不是一个容易的决定。 实施它是微不足道的,但长期影响可能最终会产生影响。

表达式 nameof(XXX) 会被编译成一个常量字符串,我认为这与 JS 完全兼容

@rayncc他的意思是:如果 JS 稍后添加一个nameof运算符,它的行为与 TypeScript 的nameof运算符不同怎么办?

然后就会发生冲突,必须以某种方式解决(对此没有任何简单的答案)。

另一方面, nameof不是 JS 中的保留字,因此 TC39 将其添加为运算符的机会非常低。 所以我认为 TypeScript 添加它是很安全的。

我不认为这种情况与将来可以在 JavaScript 中以不同方式实现的泛型或类型不同...... TypeScript 应该支持它,同时还支持实现这些概念的 TS 方式。

这有很多用例,例如 NativeScript 的 notifyPropertyChange( nameof( property) ) 方法。 顺便说一句,他们以前用装饰器的东西做过这个。 那里的解决方案是简单地将其隐藏在“实验性”编译器标志后面。

@markusmauch哈哈,看看他们的结局如何

装饰者不是很好的榜样

另一方面,nameof 不是 JS 中的保留字,因此 TC39 将其添加为运算符的机会非常低。

一元前缀运算符不需要是保留字,因为op expr已经是任何不是运算符的op的无效表达式。 例如, await以前不是保留字,但是将它添加到语言中没有任何问题,因为它只出现在明确的一元运算符位置。

我还想指出EW确实有keyof这其实是比强大得多nameof ,结果nameof只是一个stringkeyof让我们更好地限制输入。

虽然不如nameof运算符漂亮,但我们可以使用keyof使用函数 IMO 来做一些足够类似的事情:

function nameof<T>(k: keyof T) : keyof T {
    return k
}
class Person {
    static defaultName: string;
    name!: string;
    studies!: {
        highSchool: string
        unversity: string
    }
}
let foo : { bar: { baz : { x: number }}}
let k1 = nameof<Person>("name")
let k2 = nameof<Person['studies']>("unversity")
let k3 = nameof<typeof Person>("defaultName")
let k4 = nameof<typeof foo['bar']['baz']>("x")

对于简单的情况,它看起来不错,对于嵌套路径,它看起来确实有点不稳定,而且您不能在私有路径上使用它。

@dragomirtitian即使解决了 {access members by name in a type safe approach} 问题(这很棒),这不会使编译器自动帮助开发人员进行重构,也不会帮助 IDE 查找成员的引用无需进行文本搜索。

@goodmind我真的不明白你的意思。 我同意装饰器与最初预期的完全不同,但这就是“实验性”的意思。 这意味着“使用它,但要注意它可能会改变”。 无论如何,我很高兴我现在可以使用它们,当它们成为 JavaScript 语言的一部分时,我将相应地更改我的代码或保持编译器开关到位。 对 nameof 也可以这样做。 添加了装饰器支持以赢得 Googe。 但显然社区的需求并不同等重要。

@dragomirtitian在本期的折叠帖子中已经讨论过。

总而言之, keyof在您提到的场景中有所帮助,但仍然没有得到一些常见的用例,其中类或接口/类型标识符名称最好保持同步。

前任。 当使用依赖注入框架( @inject(nameof(Something)) )时,抛出很好的参数错误消息......

function add(a: number, b: number) {
    throwArgumentErrorIfNaN(a, nameof(a));
    throwArgumentErrorIfNaN(b, nameof(b));

    return a + b;
}

...,在测试描述名称中...

import { CustomCollection } from "some-library";

describe(nameof(CustomCollection), () => {
    describe(nameof<CustomCollection>(c => c.add), () => {
    });
});

...,在编写日志语句 ( logger.log(nameof(ClassName), LogLevel.Info, "Some log message.") ) 时,可能还有更多。

添加nameof肯定有一些好处,但在我看来,TypeScript 不应该这样做,因为此建议不符合此准则

  • 这可以在不根据表达式的类型发出不同的 JS 的情况下实现。

编辑:我的错,正在考虑这个非目标

> * 在程序中添加或依赖运行时类型信息,或根据类型系统的结果发出不同的代码。 相反,鼓励不需要运行时元数据的编程模式。

另外,我不知道 TC39 将它添加到 JS 的动机是什么,因为 JS 开发人员不会从中受益,因为编写nameof(Identifier)只是比在 JS 中编写"Identifier"好一点由于使用错误命名的标识符时没有编译时错误(我猜可能有运行时错误,但这不是很好)。 此外,TC39 不包括对类型域中标识符的 nameof 支持(例如接口和类型别名)。

它的位置可能是一个编译器插件(无耻插件),人们可以选择将其包含在他们的项目中。

关于这种类型的功能是否适合基于@dsherret 提到的指南的TypeScirpt我想知道是否有一种更以库为中心的方式可以被 linter 用来获取更清晰地编码意图的字符串类型代码访问变量或属性的名称。 我尝试使用 ES6 标记的​​模板文字快速处理一个概念,但不幸的是,它似乎确实可以访问原始模板字符串,包括模板参数语法,这将是提供运行时检查以及允许简单的单行代码的好方法变量的情况。 但是,基本思想对于简单的一层深度属性来说是合理的,并且语法可以说比带有传入参数的简单函数调用要好一些。

const props = {
  name : "Little John",
  slogan: "Rolls are rolls and tools are tools."
}

console.log(nameof`${props}.slogan`) // slogan
console.log(nameof`${props}.foo`) // ERROR: Property 'foo' not found on object 

最终,我认为我们真正想要的是一种更常见的方式来表达名称符号名称解析,一些编译器/转译器、linter 或至少运行时可以更容易地捕捉到这种首选顺序,以减少错误并提供更具弹性的代码需要在运行时以某种形式提供这些名称。

添加nameof肯定有一些好处,但在我看来,TypeScript 不应该这样做,因为此建议不符合此准则

  • 这可以在不根据表达式的类型发出不同的 JS 的情况下实现。

您能否澄清一下在这种情况下不同的 JS 意味着什么? 例如,如果nameof<InterfaceA>nameof<InterfaceB>都输出具有不同值但共享相同类型的字符串,这是否被视为不同?

@JLWalsh我想我可能误读了它没有根据表达式的类型发出不同的 JS(当我复制和粘贴它时,我有一半是从记忆中消失的)。 因此,采用调用表达式并发出字符串文字。 我现在不确定那一点究竟意味着什么,但是今天编程太久后我的大脑被炸了。

我在想这个非目标

  • 在程序中添加或依赖运行时类型信息,或根据类型系统的结果发出不同的代码。 相反,鼓励不需要运行时元数据的编程模式。

👍 请添加这个

@rjamesnw
对于表达式中的数字,我做了一些修改:

function nameof(selector: () => any, fullname = false) {
    var s = '' + selector;
    var m = s.match(/return\s+([A-Z0-9$_.]+)/i)
        || s.match(/.*?(?:=>|function.*?{)\s*([A-Z0-9$_.]+)/i);
    var name = m && m[1] || "";
    return fullname ? name : name.split('.').reverse()[0];
}

锁定等待 TC39 线程作为政策,因为除了抱怨 TC39 还没有做到这一点之外,实际上没有什么可谈的 🙃

此页面是否有帮助?
0 / 5 - 0 等级