Typescript: Предложение: добавьте оператор времени компиляции nameof для преобразования имен свойств и функций в строки

Созданный на 30 дек. 2014  ·  174Комментарии  ·  Источник: microsoft/TypeScript

Я хотел бы, чтобы оператор nameof рассматривался для Typescript.

Эта функция была только что добавлена ​​в описание C #, и это элегантное решение распространенной проблемы в 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 ) для удаления ВОЛШЕБНЫХ СТРОК ?

Я не понимаю, почему в этой ветке так много просьб, оправданий и вариантов использования. Это понятно - это должно быть главным приоритетом. Это самая ужасная часть JS, которую TS еще не решила.

Все 174 Комментарий

См. Также # 394 и # 1003.

Исправление проблемы с волшебной строкой - обычное требование на форумах. Надеюсь вскоре увидеть официальное предложение по этому поводу.

Да, закрывая это как обман, достаточно сказать, что мы определенно чувствуем боль, которую испытывают здесь люди.

Я не считаю это заблуждением по поводу каких-либо упомянутых проблем. Это может иметь решающее значение для поддержки расширенного минификации вместе с метапрограммированием. В настоящее время нет способа получить строку члена после минификации.
Хотя я понятия не имею, как добавить его к языку, не вступая в конфликт с другими функциями ...

В C # это работает иначе - вы получаете неминифицированную строку. В Typescript мне понадобится уменьшенная строка. Так что, возможно, это могло бы быть действительно другое предложение :-) Как unminified было бы тоже хорошо. Просто некоторые мысли ...

Согласен. Не похоже на дубликат. Оператор nameof следует рассматривать как очень хорошую идею. Наверное, «легко» реализовать тоже.
Я использую машинописный текст с angularjs, и мне нравится создавать статические строковые константы внутри моих классов контроллера / директив, которые я использую, когда регистрирую эти классы в модуле angular. Вместо жесткого кодирования этих имен я хотел бы использовать nameof (MyDirectiveClass).
Это та же проблема, с которой мы столкнулись ранее с событиями PropertyChanged в WPF (до 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 и создаю собственные идентификаторы документов по соображениям производительности и согласованности. Эти 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() (см. Пример здесь ) решит Остальные случаи, о которых я знаю, без необходимости создавать новые элементы синтаксиса. С таким же успехом он мог бы быть, например, декоратором ядра.

Пример использования, когда ни # 394, # 1003, ни getClass (), ни GetClassName () не помогли бы, - это в Angular2:

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

Это сделало бы рефакторинг кода доказательством, но также сделало бы его намного менее читаемым, поэтому я не уверен, что это такая хорошая идея. Просто мысль.

Добавление здесь предложения, чтобы иметь в виду Enums для 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 #, чтобы мы могли записать имя типа.

@RyanCavanaugh
Пять месяцев спустя:

1003 приземлился, но не помогает во многих сценариях (см. Ниже).

394 закрывается в пользу № 1295. Последний кажется далеким от приземления, и хотя он решает интересную часть проблемы (набор текста), он не помогает с частью строки / имени.

Может пора вернуться?

Строки плохи, потому что вы не можете «найти все ссылки» их, вы не можете «реорганизовать / переименовать», и они подвержены опечаткам.

Некоторые примеры с использованием 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-Enter" для своего сообщения. Я добавил несколько примеров кода из реальной жизни, которые могут принести пользу.

Чтобы усложнить ситуацию, некоторые вышеупомянутые API также принимают пути. Итак, @computedFrom('p.address.city') действителен, и было бы неплохо поддержать его ...

В этом комментарии в другом выпуске я предложил какие-то ограниченные _Expressions_ из C #.

Они были бы лучшей альтернативой, чем nameof для большинства примеров в этом потоке:

  • Они могут помочь напечатать результат;
  • Они поддерживают более сложные обращения к членам (вложенные, индексаторы);
  • Они дают вам переменную для начала, которая в некоторых случаях не будет в области видимости с nameof .

Недостатком последнего пункта является то, что выражения не охватывают такие случаи, как:

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

Но это не так часто, как другие варианты использования.

Копирую описание идеи из другого номера:


Предположим, у нас есть особый вид строк для обозначения выражений.
Назовем это type Expr<T, U> = string .
Где T - это начальный тип объекта, а U - тип результата.

Предположим, мы могли бы создать экземпляр Expr<T,U> , используя лямбду, которая принимает один параметр типа T и выполняет доступ к нему члену.
Например: person => person.address.city .
Когда это происходит, вся лямбда компилируется в строку, содержащую любой доступ к параметру, в данном случае: "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)

Большой: +1:: 100: от меня за имя поддержки.

Я использую обходные пути по причинам, указанным многими другими выше, - мне нужно что-то удобное для рефакторинга. API, которые принимают строки для идентификации имени некоторого свойства в объекте Javascript, многочисленны.

Вот реальный пример, когда 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. Не предоставляет способ исключения частных или защищенных членов (если не указано что-то вроде publicMemberof или publicOrProtectedMembersOf , но это может показаться слишком многословным). Я считаю, что это очень важная функциональность - это тот вид безопасности, который нужен программистам, и nameof был разработан для обеспечения.

Я думаю, что # 1295 интересен, но кажется в лучшем случае дополнительным (они могли бы хорошо работать вместе)? Это кажется странным решением (особенно с тегом вердикта «Отклонено», который я считаю здесь неуместным - и в целом). Я думаю, что nameof - это хороший синтаксис и концепция. Нет необходимости чрезмерно «рационализировать» или пытаться оправдать решение не реализовывать его только потому, что это может помешать будущему синтаксису EcmaScript (который, как мне кажется, является здесь основной проблемой).

Касательно: пункты 1-4. В большинстве случаев вы просто пишете "someMember" и все будет работать. Я бы использовал синтаксис <...>" " тогда, когда цель не предоставила тип memberof , что будет довольно редко.

Пункт 5 правильный.

Пункт 6 неверен; вы сможете переименовать в этой позиции.

Пункт 7 неверен, если целью является memberof - я думаю, мы сможем предоставить хорошее сообщение об ошибке.

Пункт 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 в качестве вехи) будет хорошей заменой (и более дружественным) термином «Отклонено»: если проблема будет закрыта, это будет означать, что этого «вероятно никогда не произойдет», если открыто, «может быть, когда-нибудь, кто знает».
  • «На рассмотрении» можно заменить «В обсуждении» (что несколько сбивает с толку тег «Обсуждение»).
  • «Запланировано» и «В разработке», возможно, заменит «Выполнено».
  • «Дубликат» иногда используется для предложений, когда предпочтение отдается другому подходу. На самом деле это не «дубликаты», что-то вроде «Объединенный» может сработать лучше (может быть, лучше термин, но сейчас я ничего не могу придумать, нужно больше времени). _ [Edit: возможно, что-то вроде «Конвергентного»? «Merged» может сбивать с толку с Git «Merge»] _.
  • Также «Слишком сложно» -> «Не стоит».

Я думаю, что некоторые из этих альтернатив также лучше отражают тот факт, что это не совсем проект, ориентированный на «сообщество» (скажем, как Node.js). Может показаться, что такие термины, как «В обсуждении», относятся к обсуждению «сообщества», а не команды разработчиков (по крайней мере, я так сначала думал). «Отклонено» кажется немного экстремальным (никогда не говори никогда ..). Мне кажется, что это немного характеризует запросы функций, как если бы они были «требованиями» или даже «испытаниями». Иногда это просто «идеи», мысли или элементарные выражения потребностей, точки для исследования и т. Д. Иногда они могут служить только «этапами» или источниками информации для более совершенных.

Я готов приложить некоторые личные усилия, чтобы поработать над этим. Может быть, я открою мета-проблему, когда у меня будет более полный набор терминов, который, на мой взгляд, будет достаточно хорош.

Это хороший отзыв, и похоже, что здесь есть куда улучшать. Я не большой поклонник «Незапланированного» (звучит так, будто мы пренебрегли его планированием?), Но я думаю, что список, который у вас есть, хорош. Я бы приветствовал новый список, если вы придумаете что-то менее двусмысленное и более дружелюбное, чем то, что есть сейчас.

@RyanCavanaugh

Да, я понимаю, что «незапланированный» можно также прочитать как «внеплановый», поэтому мне нужно больше времени, чтобы подумать о чем-то лучшем. В любом случае, у меня есть другие идеи (например, разделение «По замыслу» на «Предполагаемое поведение» и, может быть, что-то вроде «Ограничение дизайна»), но здесь это немного не по теме. Я планирую взять полный набор тегов, перечисленных в FAQ , изучить их использование и постараться найти альтернативы. Я открою новый выпуск, когда дойду до набора условий, которые считаю достаточно разумными.

Вот обновление для лучшего обходного пути для случая использования, который я указал выше . Мое текущее решение - полифилить свойство ES6 name для экземпляров функции , если это необходимо, для всех методов в целевом объекте и во всех его прототипах:

(Версия 5.0 V8 не требует этого полифила при нацеливании на ES6, я считаю (в 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);
        }

        // ...
}

Я не знаю универсального решения о том, как применять этот конкретный полифилл нецелевым способом, хотя он успешно решает мою конкретную потребность.


Другой, но более общий подход может быть реализован для поддержки членов с типами object (но не членов с примитивными типами, такими как string , number , boolean , null или undefined поскольку их нельзя сравнивать по ссылке), а также позволяет переназначать участников. Подход, который я использовал здесь, основан на объекте 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 для получения имени членов объекта, переменных, параметров функции, классов, интерфейсов, пространств имен и т. д.

Так что я не понимаю - будет ли какой-нибудь оператор nameof или нет? Потому что тикет закрыт, но комментарии продолжают поступать :)

Я пришел сюда, когда искал: TypeScript CallerMemberName

Ищу что-то вроде атрибута [CallerMemberName] в C #. Не совсем то же самое, что nameof, но используется в некоторых случаях для написания метода, так что вызывающему абоненту даже не нужно использовать nameof; например, уведомление об изменении какого-либо свойства и т. д.

Есть ли для этого отдельный вопрос, или он считается таким же?

@DaveEmmerson Это другая тема. CallerMemberName можно найти во время выполнения в javascript .

Эта проблема связана с записью имени переменной во время компиляции, которая недоступна во время выполнения.

@styfle, если вы читаете этот поток, это не может быть выполнено последовательно во время выполнения.

Я говорю о времени компиляции, таком как CallerMemberName в C # и как nameof. Я считаю, что у них есть аналогичный механизм, но я полагаю, что если бы эта проблема подошла к концу, это привело бы к аналогичному выводу.

Была просто мысль ... :)

я тоже не совсем понимаю это сопротивление

nameof - это просто макрос, который преобразует идентификатор имени свойства в строковый литерал на уровне синтаксиса, почему его добавление так сложно?

Я решил эту проблему с помощью плагина babel: https://github.com/enoshixi/babel-plugin-nameof

Я использую это на производственных сайтах в течение нескольких месяцев, и это сработало отлично. Наконец-то удалось поднять его на 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" во время компиляции (I я не выполняю навигацию по пути свойств внутри компонентов, поэтому мне не нужна поддержка сложных выражений). При таком подходе myObj, адрес и город можно безопасно переименовать.

Вышеупомянутое должно быть относительно легко реализовать (просто проанализируйте последний символ и заключите его в кавычки). Однако одним из недостатков является то, что у меня не будет безопасности типов для свойств, поэтому я не могу гарантировать, что myObj.enableTLAFeature является логическим. Я не уверен, как это решить, но я действительно предпочитаю, чтобы сайт вызова указывал свойство только один раз (для целей чтения, записи и создания метки). Если бы можно было передать только myObj.enableTLAFeature (как логическое значение и без оператора nameof), а затем использовать оператор callsitenameof внутри конструктора компонента (возвращая «enableTLAFeature» и не возвращая имя параметра конструктора например, "propName"), тогда это было бы здорово, но я не знаю, возможно ли это.

Что-то вроде этого могло быть очень полезно.

nameof<Foo> должен отображаться (для всех намерений и целей) как "Foo" , чтобы компилятор / эмиттер мог просто думать об этом как о строке.

но nameof<T> который отслеживает, что на самом деле представляет собой T, может быть очень полезен для контейнеров DI. Например:

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

тогда вы можете обойти проблему интерфейса для IOC:

привязка:
ioc.registerConstant<IFoo>(new Foo());

инъекция:

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

Для этого я создал небольшую экспериментальную библиотеку: https://github.com/dsherret/ts-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> против nameof<number | string>

пусть вместе

  • nameof<string> против 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) -> "обр"
  • 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) -> "firstName"

Все это уже было определено на C # и протестировано в реальной жизни, и это хорошо работает.

Я предпочитаю нотацию nameof() над nameof<> , чтобы избежать конфликтов в JSX, соответственно. TSX. Это, вероятно, самый распространенный сценарий:

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

@Liero , аналогию с C # трудно провести, потому что, ну, это два разных языка: C # не имеет типов объединений и пересечений, а также нелокальных анонимных типов, для TS все это хлеб с маслом

это не ясно мне , что заслуга за возможность получить фамилию , на котором был зарегистрирован тип, есть только 40% случаи в ТС , где такой вопрос даже имеет смысл из - за различные выражения анонимных типов

Помимо:

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

на 100% являются одними и теми же типами в TS (благодаря структурной типизации), как мне будет полезно видеть их под разными именами (по вашему мнению)?

@ 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() a, то я согласен с @Liero : решение состоит в том, чтобы игнорировать или иметь общий ответ на любое выражение анонимного типа и настоящий фактический ответ на любую скважину ссылка на определенный и именованный тип. Я не думаю, что кто-то когда-либо спросит название сложного или нечеткого типа, которое никогда не приходило мне в голову в качестве аргумента в пользу этой функции.

Я лично считаю, что реальное значение nameof не в получении информации о типе во время выполнения, а скорее в обеспечении безопасности типов при использовании строковых литералов. Как упоминалось выше, в настоящее время я использую плагин babel, реализующий nameof; вот довольно типичный для меня вариант использования (использую 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, если вам не

TS имеет дело с двумя разными доменами:

  • value domain - все, что связано с испускаемым JS, потому что каждая мелочь в JS - это объект
  • type domain - эфемерные вещи, которые помогают рассуждать о согласованности кода, которые не являются частью сгенерированного 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 было бы полезно, чтобы убедиться, что преломление не нарушает эти символы.

Итак, учитывая пример инверсии, вы могли бы сделать:

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

Интерфейсы @MeirionHughes не должны присутствовать во время выполнения, интерфейсы - это просто типы, которым были даны имена, типы - все иллюзорные сущности, единственная цель которых - помочь в проверке типов

Теперь все, что вы хотели бы сделать, - это перепрофилировать их, возложив на них дополнительные обязанности, а именно быть связями, которые держат вместе

  • ожидание времени выполнения для определенного объекта и
  • фрагмент кода, который оправдывает это ожидание, предоставляя ожидаемый объект

интерфейсы в TS не должны были делать это так, как было задумано, это не то, что они делают, у них нет бизнеса во время выполнения

вероятно, вас смущает имя 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 вот пример использования интерфейсов .

Конечно, кто-то мог бы поместить все это в константы, но если кто-то хочет использовать 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" like вместо nameof(Broom)?

@ aleksey-bykov это помешало бы вам делать "broom" вместо "Broom";)

Если серьезно, то я согласен с тем, что он не обеспечивает дополнительной защиты по сравнению с константами (как показано в примере инверсии). 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.
Это решает множество сценариев, в которых имена строк и имена типов (от 1 до 1) используются в различных средах JavaScript. Это значительно упростит повторный факторинг (имена модулей, имена контроллеров, инъекции зависимостей в 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() на выражениях типов только для компиляции (интерфейсы, абстрактные классы, встроенные определения, псевдонимы типов, определения модулей и т. Д.), Чтобы они вели себя простым и ограничивающим образом, подобным тому, что я предложил для классов выше. : просто выведите строку в том месте, где она вызывается.

Будет ли это первым, что сделает это через пустоту компиляции в среду выполнения из TS? Может быть. Но разве это плохо? Я не думаю, что это обязательно.

Я действительно не могу представить, что nameof() сможет удовлетворить все потребности концепции, поэтому я даже не буду просить, чтобы он был полнофункциональным. Но удерживать остальные 99% кейсов из-за того, что 1% проблематичен, не кажется хорошей идеей. Мы не имеем дела с тем, о чем думает Ecmascript, поэтому нам не нужно строго следовать черновику или спецификации, и поэтому мы можем принять эту _faulty_ версию, которая для меня уже 10^10*awesome .

Я защищаю дьявола здесь, когда вы приходите с предложением, вам лучше убедиться, что вы продумали все входы / выходы, вам нужно дать окончательный ответ на все возможные случаи (не только интерфейсы), будь то ошибки компилятора по умолчанию , вам нужно обосновать, почему это должно работать именно так, вам нужно объяснить это своим пользователям, вы должны быть уверены, что им это нравится, вам нужно выделить некоторые ресурсы для его разработки и поддержки, и вам нужно иметь спланировать, как вы собираетесь создавать новые функции поверх этого, или предотвратить их конфликт с ним

теперь, когда идея проста и ясна, а непосредственная выгода от нее очевидна для всех, это одна история

так что кто-нибудь сказал бы нет? хорошо, если мы говорим об одном специальном случае interface , который работает только без параметров типа, и мы действительно не знаем, что делать со всеми другими типами, потому что концепция просто не имеет смысла, не говоря уже о том, чтобы интуитивно понятно, это уже похоже на проблему ...

Теперь, если мы уверены, что эту проблему необходимо решить, то нам лучше иметь несколько примеров из реальной жизни, которые четко демонстрируют преимущества.

до сих пор не было хорошего примера того, почему нам нужен nameof для интерфейсов, кроме неподдерживаемых утверждений о том, что намного проще писать код и поддерживать

Я снова согласен с большей частью того, что вы сказали. Я бы не предлагал и не голосовал за размытую функцию. Вот почему я хотел бы видеть четко определенную, четко ограниченную простую версию этой функции, чем не видеть ее вообще.

Я не понимаю, как опубликованные здесь примеры считаются «неподдерживаемыми утверждениями». Большинство из них представляют собой сценарии реального мира, особенно IoC, используемые во многих проектах с участием множества различных библиотек и фреймворков. Возможность писать лучший код и с легкостью поддерживать его - вот почему с самого начала был создан Typescript. У нас, например, нет HKT, но у нас все еще есть TS, достигающий v2.0: 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 и другие отличные ребята, они в конце концов примут правильное решение. Соберем еще несколько вариантов использования

_ nameof _ отчаянно нужен, если вы, как и я, используете базу данных NoSQL и выполняете множество запросов по полям, указанным в строке, которые теряют рефакторинг, согласованность, отлавливание ошибок и автозаполнение в тот момент, когда вы входите в причудливую область Stringland.

этот выпуск, открытый с 2014 года, на самом деле представляет собой пословицу: лучшее - враг лучшего

--Р

Пример 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",
            })
    })

Я думаю, что многие варианты использования здесь можно было бы реализовать с помощью декораторов, если бы они были более общими. Если декораторы функций когда-либо будут реализованы, они смогут охватить этот вариант использования.

Многие сценарии, описанные в этом потоке, должны обрабатываться оператором keyof представленным в https://github.com/Microsoft/TypeScript/pull/11929

keyof - это потрясающе! Но все же нужно что-то для атрибутов имени при привязке данных: <input name={nameof(this.state.firstName)} />

Да, приятно видеть некоторые результаты в этой области. Но вариант использования для keyof другой. Вы по-прежнему не можете ссылаться на имя:

  • Упомянутые свойства и члены, такие как
  • Введите имена, см. Комментарий
  • Параметры

@mhegazy

keyof - это хорошо, но этого недостаточно. nameof прежнему будет _extremely_ полезен, например, при объявлении зависимостей вычисляемых свойств в Aurelia:

@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 также эти предоставленные вами примеры не решают «проблему», заключающуюся в наличии ссылок на код внутри строк, которые практически невозможно исправить автоматически или легко. Основная цель nameof в моем PoV - сделать эту строку доступной для рефакторинга и поиска.

@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);

Источник детской площадки

Когда я пытаюсь использовать Rename Symbol (F2) в коде vs, он не переименовывает мою строку, чего я ожидал бы от функции 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 это невозможно.

Когда я пытаюсь использовать символ переименования (F2) в коде vs, он не переименовывает мою строку, чего я ожидал бы от функции 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] выводится из string , чему lastName удовлетворяет.
Ваш пример хорош только потому, что каждое свойство имеет разный тип.

Действительно, но, по крайней мере, вы получаете ошибку времени компиляции, сообщающую вам, что вы должны обновить эти сайты использования. Это делает операцию рефакторинга безопасной, если не оптимально удобной.

В большинстве случаев, но не всегда. Вы можете вносить изменения, которые приводят к конфликту имен, и при этом компилировать. Это похоже на переименование метода, чтобы ввести другой метод с тем же именем, что и старый.

Еще более важный для меня инструмент - «Найти все ссылки», который просто не работает. Его заменой является простой текст _Find_, но (а) вам нужно выработать привычку никогда больше не использовать _Find ссылки_, что является позором, и (б) для общих имен _Find_ очень шумно и вызывает массу ложных срабатываний.

Голосуйте за №11997!

@RyanCavanaugh : как keyof может помочь, например, с immutable.js? вы не можете расширить все неизменяемые классы. nameof может помочь с этим. Что касается Angular2 с машинописным текстом, а для производительности он также идет с immutable.js, nameof имеет важное значение .

@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, точки расширения - это диагностика, исправления и рефакторинг, которые помогают во время разработки и не имеют риска использовать какие-либо из них. Последнее, что я хочу сделать, это добавить случайные пакеты, которые изменяют самую суть конвейера сборки , компиляцию и поддержку этих несуществующих языковых конструкций с помощью различных хаков .
И затем возникают проблемы типа «мы не можем перейти на TypeScript 2.x, потому что« nameof package »0.1.2 еще не поддерживает его, поэтому 1) нам придется подождать или 2) удалить все использования nameof из наш код и потерять его; или 3) заново реализовать его и поддерживать вечно »через несколько месяцев.

@ Peter-Juhasz, кто заставляет вас скачать это с npm? сделай сам и будь самим собой, круглосуточная служба поддержки клиентов 7 дней в неделю

api трансформации имеет мало общего с этими обстоятельствами

@ Peter-Juhasz Я сочувствую вам и согласен с тем, что nameof было бы полезно и добавило безопасности. Однако TypeScript предназначен для моделирования ECMAScript, а не для создания нового лучшего языка. Такие функции, как keyof являются чисто типовым уровнем, поэтому они внедряются. И даже они сопротивляются выделению ключевого слова. Напротив, nameof - это строго конструкция уровня выражения. Я не думаю, что команду когда-либо убедят реализовать его, если только это не указано в спецификации ECMAScript. Мне самому не нравится идея убирать идентификатор nameof .

Это оставляет нам два варианта:

  • сделайте предложение ECMAScript и надейтесь, что оно получит поддержку
  • использовать общедоступный API Transfroms и иметь хорошее приближение

На первый вариант уйдут годы (если он когда-нибудь будет одобрен), второй должен быть достаточно приличным. У меня нет опыта использования TS API, но я ожидаю, что они будут стабильными и обратно совместимыми, поэтому _nameof package_ также должен быть стабильным.

Я не согласен с Марин, nameof - это не языковая функция, она только позволяет
проверка типов (аналогично keyof), после компиляции nameof исчезает и его
аргумент остается в виде строки.
Я не понимаю, почему это должно быть частью ecmascript, если keyof этого не делает.

14 февраля 2017 г. в 19:19 «Марин Маринов» [email protected] написал:

@ Peter-Juhasz https://github.com/Peter-Juhasz Я сочувствую тебе, и я сочувствую
согласен, что nameof было бы полезно и добавило безопасности. Однако TypeScript
о моделировании ECMAScript, а не о создании нового лучшего языка. Функции
такие как keyof являются чисто типовым уровнем, поэтому они получают
реализовано. И даже для них есть сопротивление со стороны команды.
выделить ключевое слово. Напротив, nameof - это строго уровень выражения
построить. Я не думаю, что команду когда-нибудь убедят реализовать это,
если это не указано в спецификации ECMAScript. Мне самому не нравится идея
очистка идентификатора nameof.

Это оставляет нам два варианта:

  • сделайте предложение ECMAScript и надейтесь, что оно получит поддержку
  • использовать общедоступный API Transfroms и иметь хорошее приближение

На первый вариант уйдут годы (если он когда-нибудь будет одобрен), на второй
должно быть достаточно приличным. У меня нет опыта использования TS API, но
Я ожидал, что они будут стабильными и обратно совместимыми, поэтому имяпакет также должен быть стабильным.

-
Вы получаете это, потому что подписаны на эту беседу.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/Microsoft/TypeScript/issues/1579#issuecomment-279790055 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/ABOZ9c-85P3x5AMakVDuxlNkH56me3POks5rcfAYgaJpZM4DNVgi
.

По крайней мере, для интерфейсов, которые перестают работать после компиляции (код не создается и не влияет на стандарт JS)
у нас могут быть вспомогательные функции, которые предоставляют все свои метаданные.
Это довольно тривиально (сейчас я делаю это с помощью сценария bash), и
Было бы действительно полезно иметь не только nameOf, но и реализует / расширяет информацию, список полей, имена полей и их арность (подпись).

Сообщите мне, если интересно, мы могли бы написать предложение и в худшем случае препроцессор.
(если позволяет время и мотивация)

--Р

@gcnew :
1. nameof никогда не будет в спецификации ES . Я вполне уверен в этом, потому что nameof не имеет особого смысла в EcmaScript. И вот почему: из-за динамического набора текста в javascript nameof(myObject.MyProperty) в точности совпадает с "myProperty" . Оба в основном представляют собой «волшебные струны». Нет причин использовать подробный синтаксис nameof в javascript. Или вы видите какие-либо преимущества использования nameof в javascript?

Как упоминал @RyanCavanaugh , только стандартизованные функции или функции ES, которые никогда не будут в спецификации ES, могут быть рассмотрены для машинописного текста, чтобы избежать путаницы, как это случилось с модулями. Это второй случай.

  1. Самая основная цель TypeScript - обеспечить статическую типизацию и улучшить инструменты рефакторинга. Имена свойств требуются очень часто в сценариях DataBinding, и выполнение этого безопасным способом - это именно то, что вы ожидаете от TypeScript .

Я прагматичный программист, и меня не волнует, как это можно сделать, если это возможно. Выберите то, что, по вашему мнению, лучше всего подходит для продукта с учетом совместимости с ES, простоты использования, производительности или того, что вы считаете актуальным. Если вы решили использовать Transfroms API, пусть будет так, но, ради бога, этот вопрос заслуживает решения.

@Liero Вы никогда не узнаете, будет ли в будущих версиях использоваться действительный идентификатор. Он может не использоваться ECMA напрямую или не для целей, обсуждаемых здесь, но он может найти свое применение в API-интерфейсах браузера. Рассмотрим глобальную функцию nameof(node: Node): string | undefined которая читает атрибут name узла DOM. Будет ли когда-нибудь стандартизирована такая функция? Наверное, не _это_, но, может быть, касается сертификатов или Symbol s. Дело в том, что 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 машинописного текста и nameof будущей экмы, то я бы сказал, что тот же аргумент применим практически ко всему машинописному тексту, который не является в пересечении с черновиками es8, которые, по сути, представляют собой множество шрифтов, что делает этот аргумент довольно надуманным.

Желание

Даже если мы не пойдем по пути ключевых слов, это в значительной степени подводит итог для меня, почему я все еще считаю, что это оптимальная задача машинописного текста для этого (будь то преобразование, функция уровня компиляции, структура уровня типа, предложение ecma и т. Д.) :

Самая основная цель TypeScript - обеспечить статическую типизацию и улучшить инструменты рефакторинга. Имена свойств требуются очень часто в сценариях DataBinding, и выполнение этого безопасным способом - это именно то, что вы ожидаете от TypeScript.

Предположение

<nameof options.easing> 'easing'

Похоже, он почти делает это, но нам все равно пришлось бы вручную реорганизовать эту не очень волшебную строку, с той единственной разницей, что мы могли бы заставить компилятор сказать нам, что это значение не представляет фактическое имя идентификатора . Это поток refactor->compile->see_error->go_back_and_properly_refactor вместо refactor_properly one. При этом я предпочитаю настаивать на более полной версии вместо этого предложения. Теперь, @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 .

Хорошо, а что, если есть оператор nameof со стороны типа?

Что-то вроде этого:

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

Которая будет вести себя точно так же, как

function abc(thing:"x"){ }

Это не так чисто, как nameof(accessor)->string , но я думаю, что это может быть весьма полезно.

Я рассматриваю nameOf как ключевое слово времени компиляции и не вижу никакой среды выполнения или глобального использования его (никогда не говори никогда, но это маловероятно, ИМО - назовите это TSNameOf , и это еще более маловероятно)

Стоит ли бояться столкновений? Если или когда EcmaScript станет языком, которому в будущем понадобится ключевое слово nameOf , я предполагаю, что он до сих пор развился и принял все функции TS, так что TS нам больше не понадобится.

nameOf в TS должен вести себя точно так же, как nameOf в C #

Спецификации C # (v5):

Выражение nameof является константой. Во всех случаях nameof (...) вычисляется во время компиляции для создания строки. Его аргумент не оценивается во время выполнения и считается недостижимым кодом (однако он не выдает предупреждения о «недостижимом коде»).

К настоящему времени я потратил слишком много времени на «рефакторинг» строковых литералов в моем коде TS / Angular. В конце концов, основная цель Typescript - быть альтернативой со строгим контролем типов, верно?

Поторопитесь, добавьте ключевое слово nameOf время компиляции, пока TS не устареет :)

А как насчет пользовательской функции 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 и компилятор машинописного текста.

За свои два цента я бы хотел увидеть nameof исключительно для того, чтобы избавиться от строк в привязках с помощью Inversify.

Я как раз тоже этим занимался. Я надеялся, что к настоящему времени это станет очевидной функцией. Я надеялся получить имена строк событий из имен методов в настраиваемой настройке события (bla bla bla;)), среди прочего (например, для хранения пространства имен и метаданных типа и т. Д.). Надеюсь, нам не придется обсуждать это еще два года.

Я тоже хотел бы это увидеть. Я работаю с некоторым устаревшим кодом 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

обратите внимание, что это не идеально. Я пытался использовать подобные вещи, но это не удалось, потому что интерфейсы были слишком сложными, и компилятор вместо генерации "field1" | "field2" для keyof T сгенерировал что-то вроде any , и все будет компилироваться.

Спасибо!
Искра Риддла

Есть ли консенсус относительно того, как это делать с классами и интерфейсами? Я вижу, как могу просто keyof но в основном я хочу использовать nameof (type), чтобы упростить ведение журнала, например, LogManager.getLogger(nameof(MyClass))

@niemyjski Это доступно со стандартным JavaScript.

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

См. Function.name

@styfle Это не удается, если код минифицирован, поэтому он запрашивает оператор времени компиляции, который создает строку на выходе и не будет затронут минификацией

@dpogue большинство

Некоторых также может удивить, что nameof(MyClass) != MyClass.name (в минифицированном случае)

Кроме того, у # 8 и # 16037 могут быть какие-то связанные обсуждения, за которыми нужно следить.

Да, нужно что-то надежное ...

Спасибо
-Блейк Немийски

В среду, 19 июля 2017 г., в 11:47, Ян Маклауд [email protected]
написал:

@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 ) для удаления ВОЛШЕБНЫХ СТРОК ?

Я не понимаю, почему в этой ветке так много просьб, оправданий и вариантов использования. Это понятно - это должно быть главным приоритетом. Это самая ужасная часть JS, которую TS еще не решила.

+1

это была бы полезная функция

Так что рассказывают люди, это происходит?

Как упоминал ранее @jakkaj , это будет действительно отличная функция для использования с inversify .

Вот пример того, что вам _currently_ нужно делать, когда вы используете интерфейсы:

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()
};

и последствия будут такими же (весь код потребления должен ссылаться на свойство Weapon объекта types ).

С помощью оператора 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) { }
}

Если предлагаемая функциональность аналогична оператору nameof в C #, это приведет к @inject("Weapon") , что приведет к скрытым глобальным зависимостям и конфликтам токенов.

Что-то вроде

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

кажется предпочтительным, но с тех пор он все еще не идеален. Проблема заключается в том, чтобы синхронизировать имена для поддержки слияния деклараций, но это не связано с фактическими значениями.

Есть много других причин хотеть nameof , но я не думаю, что это хорошо подходит для DI.

@aluanhaddad

Скорее всего, это не будет иметь желаемого эффекта, поскольку каждый раз, когда вызывается символ, он создает новое уникальное значение.

Ах да, это правда. Symbol.for исправит это , но, как вы отметили, имя, которое вы используете для символа, в некоторой степени не имеет значения, если вы создаете символ только один раз и экспортируете его.

это приведет к @inject("Weapon") , что приведет к скрытым глобальным зависимостям и конфликтам токенов.

Да, существует вероятность столкновения токенов, но в небольшом проекте, где все интерфейсы имеют уникальное имя, он будет работать отлично (а InversifyJS упрощает управление, как я недавно обнаружил 😁).

Я вижу, что nameof хорошо подходит для внедрения зависимостей, _ если вы знаете об ограничениях_. Точно так же, как кто-то упоминал ранее в этом потоке, будут ограничения при использовании nameof с минимизированным кодом. Например, вы можете использовать nameof для получения имени параметра. Во время минификации имя параметра изменяется, но сгенерированная компилятором «волшебная строка» сохраняет исходное имя. Может быть, вам нужно исходное имя, или, может быть, вам нужно изменить имя. Пока вы знаете об ограничениях, связанных с nameof , все будет в порядке.

+1

Вот еще пара хакерских обходных функций, которые пока работают для моих целей, которые могут получить имя или путь вложенной опоры с помощью статической проверки.

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 С какой стати вы хотите присвоить типу имя «Мем» (теоретически строка)? Это бессмысленно. Вы имели в виду «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, то мы могли бы указать плагин преобразования (пример названия реализации здесь ) в 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 ()
class Ninja {
constructor (@inject (nameof (Weapon)) weapon: Weapon) {}
}

Именно то, что я хочу

@rayncc не нужно использовать здесь nameof если вы используете фреймворк, который полагается на декораторы, такие как TypeScript IOC, Angular 2+ или @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 который ведет себя иначе, чем оператор nameof TypeScript?

Тогда возникнет конфликт, который нужно как-то разрешить (а простых ответов на это нет).

С другой стороны, nameof не является зарезервированным словом в JS, поэтому шансы, что TC39 добавит его в качестве оператора, довольно низки. Так что я думаю, что TypeScript довольно безопасно добавить его.

Я не думаю, что этот случай отличается от дженериков или типизаций, которые могут быть реализованы в JavaScript другим способом в будущем ... TypeScript тогда должен поддерживать его, а также поддерживать способ TS для реализации этих концепций.

Для этого существует множество вариантов использования, например метод notifyPropertyChange ( nameof (property) ) в NativeScript. Кстати, они делали это раньше с помощью декораторов. Решение заключалось в том, чтобы просто спрятать его за «экспериментальным» флагом компилятора.

@markusmauch lol, посмотрите, чем это закончилось для них

декораторы - не лучший пример для подражания

С другой стороны, nameof не является зарезервированным словом в JS, поэтому шансы, что TC39 добавит его в качестве оператора, довольно низки.

Унарные префиксные операторы не должны быть зарезервированными словами, потому что op expr уже является недопустимым выражением для любого op , не являющегося оператором. Например, await ранее не было зарезервированным словом, но не было никаких проблем с его добавлением в язык, потому что оно всегда появлялось только в однозначной позиции унарного оператора.

Могу ли я также отметить, что у ew действительно есть keyof который на самом деле намного мощнее, чем nameof , результатом nameof будет просто string , keyof позволяет нам намного лучше ограничивать ввод.

Хотя это не так красиво, как оператор 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 даже при том, что это

@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) лишь немного лучше, чем писать "Identifier" в JS. из-за отсутствия ошибок времени компиляции при использовании неверно названного идентификатора (я думаю, могут быть ошибки времени выполнения, но это не так хорошо). Кроме того, TC39 не будет включать поддержку nameof для идентификаторов в домене типа (например, имен интерфейса и псевдонимов типа).

Его место, вероятно, должно быть плагином компилятора (бессовестным плагином), который люди могут выбрать для включения в свои проекты.

Что касается того, подходит ли этот тип функции для TypeScirpt на основе рекомендаций, упомянутых @dsherret, мне интересно, есть ли более ориентированный на библиотеку способ, который мог бы использоваться линтерами для выбора строго типизированного кода, который более четко кодирует намерение для доступа к имени переменной или свойства. Я попытался быстро поиграть с концепцией с использованием шаблонных литералов с тегами 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 

В конечном счете, я думаю, что нам действительно нужен более распространенный способ выразить разрешение имени символа имени, которое может быть легче уловлено каким-либо компилятором / транспилятором, линтером или, по крайней мере, средой выполнения в этом предпочтительном порядке, чтобы уменьшить количество ошибок и обеспечить более устойчивую код должен иметь эти имена в той или иной форме во время выполнения.

Определенно есть некоторая выгода от добавления 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 рейтинги