Typescript: Разрешить индексацию с помощью символов

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

TypeScript теперь имеет целевой режим ES6, который включает определения Symbol . Однако при попытке проиндексировать объект с помощью символа я получаю сообщение об ошибке (аргумент индексного выражения должен иметь тип «строка», «число» или «любой»).

var theAnswer = Symbol('secret');
var obj = {};
obj[theAnswer] = 42; // Currently error, but should be allowed
Moderate Fix Available Suggestion help wanted

Самый полезный комментарий

Машинопись 3.0.1, укусила это.
Мне нужна запись, которая принимает symbol но TS мне не разрешает.

С момента открытия этого номера прошло 3,5 года, а можно нам символы

Ирония заключается в том, что TS противоречит сама себе.
TS раскрывает keyof any = number | string | symbol .

Но когда вы делаете record[symbol] TS отказывается говорить
_Тип 'символ' нельзя использовать в качестве индексатора_.

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

Это часть поддержки символов ES6, над которой работает

@wereHamster , с запросом на вытягивание # 1978, это должно стать законным, и obj[theAnswer] будет иметь тип any . Этого достаточно для того, что вы ищете, или вам нужен более строгий набор текста?

Можно ли будет указать тип свойств, которые индексируются символами? Примерно так:

var theAnswer = Symbol('secret');
interface DeepThought {
   [theAnswer]: number;
}

Судя по комментариям в этом PR, нет:

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

Я думаю, что @wereHamster говорит о более сильном типировании, чем @danquirk. Здесь есть 3 уровня поддержки. Самый базовый уровень обеспечивается моим PR, но это только для символов, которые являются свойствами глобального объекта Symbol, а не для символов, определенных пользователем. Так,

var theAnswer = Symbol('secret');
interface DeepThought {
    [Symbol.toStringTag](): string; // Allowed
    [theAnswer]: number; // not allowed
}

Следующим уровнем поддержки будет разрешение индексатора символов:

var theAnswer = Symbol('secret');
interface DeepThought {
   [s: symbol]: number;
}
var d: DeepThought;
d[theAnswer] = 42; // Typed as number

Это находится на нашем радаре и может быть легко реализовано.

Самый сильный уровень - это то, о чем вы просите, что-то вроде:

var theAnswer = Symbol('secret');
var theQuestion = Symbol('secret');
interface DeepThought {
   [theQuestion]: string;
   [theAnswer]: number;
}
var d: DeepThought;
d[theQuesiton] = "why";
d[theAnswer] = 42;

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

С моим PR вы, по крайней мере, должны иметь возможность использовать символ для извлечения значения _out_ объекта. Это будет any , но вы больше не получите ошибку.

@wereHamster Я сделал небольшую запись # 2012, которая может вас заинтересовать.

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

@wereHamster, можете ли вы опубликовать

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

Мы бы взяли за это пиар. @JsonFreeman может предоставить подробную информацию о некоторых проблемах, с которыми вы можете столкнуться.

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

@RyanCavanaugh , было бы неплохо, в конце концов, добавить последний пример в https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456 typecheck. Но при желании вы можете разделить эту проблему на несколько более мелких задач, которые накладываются друг на друга.

Были ли обновления по этому поводу? AFAIU последняя версия компилятора поддерживает только первый уровень, описанный в https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456.

Мы будем рады принять PR на это изменение.

Возможно, стоит отслеживать эти два уровня как две отдельные проблемы. Индексаторы кажутся довольно простыми, но их полезность непонятна. Полная поддержка с постоянным отслеживанием кажется довольно сложной, но, вероятно, более полезной.

Постоянное отслеживание уже отслеживается в https://github.com/Microsoft/TypeScript/issues/5579. эта проблема связана с добавлением поддержки индексатора символов, подобного индексаторам строк и чисел.

Понятно, имеет смысл.

@JsonFreeman @mhegazy проблема доступна по адресу # 12932

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

Поскольку символы не могут быть указаны в качестве ключей индекса, в отличие от того, что явно разрешено в JavaScript, я вынужден привести к <any> в нескольких местах, что ухудшает качество кода.

interface Query {
  [key: string|symbol]: any;
}

const Q = {
  startsWith: Symbol('startsWith'),
  gte: Symbol('gte'),
  lte: Symbol('lte')
}

const sample: Query = {
  name: {
    [Q.startsWith]: 'M',
    length: {
      [Q.lte]: 25
    }
  },
  age: {
    [Q.gte]: 18
  }
};

Использование «маловероятных» первых символов, таких как символ $ не является подходящим компромиссом, учитывая разнообразие данных, которые может потребоваться обработчику запросов для проверки.

Привет, ребята. Есть ли в этом движение? Мне это нужно, поэтому я буду рад внести необходимые изменения. Но раньше не участвовал в TS.

@mhegazy @RyanCavanaugh Я знаю, что вы, ребята, невероятно заняты, но не могли бы вы взвеситься, когда у вас появится шанс? Символы - действительно важный инструмент для создания архитектуры библиотек и фреймворков, и отсутствие возможности использовать их в интерфейсах - определенная проблема.

Я спрашиваю, что-то в процессе? Искренне надеюсь, что эта функция будет поддерживаться.

https://github.com/Microsoft/TypeScript/pull/15473 выглядит связанным.

Да, я все еще ищу это сегодня, вот что я вижу в Webstorm:

screenshot 2017-10-08 21 37 17

Это действительно работает

var test: symbol = Symbol();

const x = {
    [test]: 1
};

x[test];

console.log(x[test]);

console.log(x['test']);

но тип x неверен, так как

{
  [key: string]: number
}

Да, я все еще ищу это сегодня, вот что я вижу в Webstorm:

Обратите внимание, что собственный языковой сервис JetBrains включен по умолчанию в WebStorm, intelliJ IDEA и т. Д.

Это работает в TS 2.7

const key = Symbol('key')
const a: { [key]?: number } = {}
a[key] = 5

Есть новости по этому поводу?

Моя проблема:

export interface Dict<T> {
  [index: string]: T;

  [index: number]: T;
}

const keyMap: Dict<number> = {};

function set<T extends object>(index: keyof T) {
  keyMap[index] = 1; // Error Type 'keyof T' cannot be used to index type 'Dict<number>'
}

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

export interface Dict<T> {
  [index: string]: T;
  [index: symbol]: T; // Error: An index signature parameter type must be 'string' or 'number'
  [index: number]: T;
}

Ожидаемое поведение:
symbol должен быть допустимым типом индекса

Фактическое поведение:
symbol не является допустимым типом индекса

Использование обходного пути с приведением as string | number кажется мне очень плохим.

Как предполагается использовать util.promisify.custom в TypeScript? Кажется, что использование постоянных символов теперь поддерживается, но только если они явно определены. Итак, это действительный TypeScript (за исключением того, что f не инициализируется):
typescript const custom = Symbol() interface PromisifyCustom<T, TResult> extends Function { [custom](param: T): Promise<TResult> } const f: PromisifyCustom<string, void> f[custom] = str => Promise.resolve()
Но если promisify.custom используется вместо custom , попытка ссылки на f[promisify.custom] приводит к ошибке Element implicitly has an 'any' type because type 'PromisifyCustom<string, void>' has no index signature. :
typescript import {promisify} from 'util' interface PromisifyCustom<T, TResult> extends Function { [promisify.custom](param: T): Promise<TResult> } const f: PromisifyCustom<string, void> f[promisify.custom] = str => Promise.resolve()
Я хотел бы присвоить функции поле promisify.custom , но кажется (с учетом поведения, описанного выше), единственный способ сделать это - привести функцию к типу any .

Я не могу понять, почему символ не разрешен в качестве индекса ключа, приведенный ниже код должен работать и принимается Typescript 2.8, но не разрешен Typescript 2.9

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: string ]: V } = {};

  public has (k: K): boolean {
    return k in this.o;
  }

  public get (k: K): V {
    return this.o[k as PropertyKey];
  }

  public set (k: K, v: V) {
    this.o[k as PropertyKey] = v;
  }

  public getMap (k: K): V {
    if (k in this.o) {
      return this.o[k as PropertyKey];
    }
    const res = new SimpleMapMap<K, V>();
    this.o[k as PropertyKey] = res as any as V;
    return res as any as V;
  }

  public clear () {
    this.o = {};
  }
}

Я пробовал ниже, что для меня более `` правильно '', но оно не принимается обеими версиями компилятора Typescript

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: K ]: V } = {};

  public has (k: K): boolean {
    return k in this.o;
  }

  public get (k: K): V {
    return this.o[k];
  }

  public set (k: K, v: V) {
    this.o[k] = v;
  }

  public getMap (k: K): V {
    if (k in this.o) {
      return this.o[k];
    }
    const res = new SimpleMapMap<K, V>();
    this.o[k as PropertyKey] = res as any as V;
    return res as any as V;
  }

  public clear () {
    this.o = {};
  }
}

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

@beenotung Хотя это не идеальное решение, если предположить, что опубликованный вами класс - единственное место, где вам нужно такое поведение, вы можете выполнять небезопасные преобразования внутри класса, но сохраняя сигнатуры класса и методов одинаковыми , чтобы потребители этого класса не увидят:

/**
 * Key can only be number, string or symbol
 * */
export class SimpleMapMap<K extends PropertyKey, V> {
  private o: { [k: string]: V } = {};

  public has(k: K): boolean {
    return k in this.o;
  }

  public get(k: K): V {
    return this.o[k as any];
  }

  public set(k: K, v: V) {
    this.o[k as any] = v;
  }

  public getMap(k: K): V {
    if (k in this.o) {
    return this.o[k as any];
    }

    const res = new SimpleMapMap<K, V>();
    this.o[k as any] = res as any as V;
    return res as any as V;
  }

  public clear() {
    this.o = {};
  }
}

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

Пример потребителя приведен ниже (код не потребуется изменять, когда эта проблема будет устранена):

const s1 = Symbol(1);
const s2 = Symbol(2);

let m = new SimpleMapMap<symbol, number>()
m.set(s1, 1);
m.set(s2, 2);
m.get(s1);
m.get(1); //error

Машинопись 3.0.1, укусила это.
Мне нужна запись, которая принимает symbol но TS мне не разрешает.

С момента открытия этого номера прошло 3,5 года, а можно нам символы

Ирония заключается в том, что TS противоречит сама себе.
TS раскрывает keyof any = number | string | symbol .

Но когда вы делаете record[symbol] TS отказывается говорить
_Тип 'символ' нельзя использовать в качестве индексатора_.

Да, я долго мучился с этим, к сожалению, мой последний вопрос по этой теме:

https://stackoverflow.com/questions/53404675/ts2538-type-unique-symbol-cannot-be-used-as-an-index-type

@RyanCavanaugh @DanielRosenwasser @mhegazy Есть обновления? Этому выпуску скоро исполнится четвертый год.

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

@jhpratt есть PR по адресу # 26797 (обратите внимание на предостережение об известных символах). Об этом есть недавние заметки на совещании по дизайну в # 28581 (но никакого разрешения там не записано). Там немного больше обратной связи о том, почему , что PR держится здесь . Похоже, это рассматривается как второстепенная / незначительная проблема, поэтому, возможно, большее количество голосов по PR может помочь поднять профиль проблемы.

Спасибо @yortus. Я только что спросил Райана, планируется ли PR для версии 3.2, что и обозначено вехой. Надеюсь, что это так, и этот вопрос будет решен!

PR, на который указывает @yortus, кажется огромным изменением,
Разве исправление этой ошибки не должно быть очень незначительным? например, добавление оператора или при проверке состояния.
(Я еще не нашел место, где можно изменить.)

временное решение здесь https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -412287117, вроде уродливо, но выполняет свою работу

const DEFAULT_LEVEL: string = Symbol("__default__") as any;

другой https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -460650063, так как линтеры h8 any

const ItemId: string = Symbol('Item.Id') as unknown as string;
type Item = Record<string, string>;
const shoes: Item = {
  name: 'whatever',
}
shoes[ItemId] = 'randomlygeneratedstring'; // no error
{ name: 'whatever', [Symbol(Item.Id)]: 'randomlygeneratedstring' }

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

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


Изменить: оказывается, этот подход работает только с Record<X,Y> но не с Interfaces . На данный момент закончился использованием // @ts-ignore поскольку он все еще синтаксически корректен и по-прежнему хорошо компилируется в JS, как и должен.

Однако следует отметить, что при использовании // @ts-ignore в строках, содержащих символы, на самом деле возможно (и помогает) вручную указать тип этого символа. VSCode по-прежнему понимает это.

const id = Symbol('ID');

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

const alice: User = {
  name: 'alice',
  age: 25,
};

// @ts-ignore
alice[id] = 'maybeSomeUUIDv4String';

// ...

// then somewhere, when you need this User's id

// @ts-ignore
const id: string = alice[id];

console.log(id); // here you can hover on id and it will say it's a string

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

Однако мое время ограничено, и я ничего не знаю об источниках Typescript. Я сделал форк (https://github.com/Neonit/TypeScript), но пока не получил пул-реквест, потому что не хочу приставать к разработчикам незавершенными (?) Изменениями. Я прошу всех внести свой вклад в мою вилку. Тогда я в конце концов оформлю PR.

Пока что я нашел способ исправить ограничение типа индекса интерфейса. Не знаю, есть ли что-то еще. Мне удалось проиндексировать объект с символом в TS 3.4 без каких-либо исправлений. ( https://www.typescriptlang.org/play/#src = const% 20o% 20% 3D% 20% 7B% 7D% 3B% 0D% 0Aconst% 20s% 20% 3D% 20 Символ ('s')% 3B % 0D% 0A% 0D% 0Ao% 5Bs% 5D% 20% 3D% 20123% 3B)

Взгляните на мою фиксацию, чтобы увидеть, что я нашел: https://github.com/Neonit/TypeScript-SymbolKeys/commit/11cb7c13c2494ff32cdec2d4f82673058c825dc3

Отсутствует:

  • Тесты: у меня не было времени разобраться, как организованы и структурированы тесты в TS.
  • Локализация: диагностическое сообщение обновлено только для английского варианта. Может быть, другие языки все еще понимают старое сообщение. Я не знаю. В любом случае я мог предоставить только немецкий перевод.

Я надеюсь, что после долгих лет ожидания это наконец-то поможет.

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

привет, есть ли в этом прогресс?

Просто открыл тему SO об этом: https://stackoverflow.com/questions/59118271/using-symbol-as-object-key-type-in-typescript

Почему это невозможно? Разве symbol еще одним примитивным типом, например number - так в чем разница?

Привет, есть ли в этом прогресс?

Прошло ПЯТЬ лет!

Вы не поверите, сколько времени понадобилось C ++, чтобы получить замыкания 😲

лол честно, но C ++ не рекламирует себя как надмножество языка, который имеет закрытие :-p

@ljharb продолжай бить эту лошадь, она все еще дергается 😛

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

let m = new Map<symbol, number>();
let s = Symbol("arbitrary symbol!");

m.set(s, 1000);
let a = m.get(s);


Карты и объекты имеют разные варианты использования.

В качестве протоколов используются известные символы Symbol.match , например, не сделает объект похожим на RegExp (и любому объекту может потребоваться ключ Symbol.iterable чтобы сделать его повторяемым без необходимости явно использовать встроенный TS Итерируемые типы).

Почти 5 лет (

Пожалуйста, внедрите эту функцию, я не могу нормально писать код ..

Могут ли участники привести реальные примеры из своих сценариев использования?

Я не понимаю пример протокола и почему это невозможно сегодня.

Вот пример StringConvertible

const intoString = Symbol("intoString")

/**
 * Something that can be converted into a string.
 */
interface StringConvertible {
    [intoString](): string;
}

/**
 * Something that is adorable.
 */
class Dog implements StringConvertible {
    [intoString](): string {
        return "RUFF RUFF";
    }
}

/**
 * <strong i="9">@see</strong> {https://twitter.com/drosenwasser/status/1102337805336768513}
 */
class FontDog implements StringConvertible {
    [intoString](): string {
        return "WOFF WOFF";
    }
}

console.log(new Dog()[intoString]())
console.log(new FontDog()[intoString]())

Вот пример Mappable или Functor (за исключением отсутствия конструкторов типов более высокого порядка):

const map = Symbol("map")

interface Mappable<T> {
    [map]<U>(f: (x: T) => U): Mappable<U>
}

class MyCoolArray<T> extends Array<T> implements Mappable<T> {
    [map]<U>(f: (x: T) => U) {
        return this.map(f) as MyCoolArray<U>;
    }
}

@DanielRosenwasser, похоже, вы предполагаете, что все объекты имеют интерфейс, являются экземпляром класса или известны заранее; используя ваш последний пример, я смогу установить map , скажем, на любой объект javascript (или, по крайней мере, на объект, тип которого позволяет добавлять к нему любой символ), что затем делает его отображаемым.

Установка свойства (символа или нет) на объект постфактум является частью другого запроса функции (часто называемого «свойствами раскрытия» или «типами раскрытия»).

При отсутствии этого тип, который вам понадобится для подписи индекса символа, будет очень мало обеспечивать пользователю TypeScript, верно? Если я правильно понимаю, тип должен быть чем-то вроде unknown или просто any чтобы быть в некоторой степени полезным.

interface SymbolIndexable {
   [prop: symbol]: any; // ?
}

В случае протоколов это обычно функция, но, конечно, это может быть unknown .

Мне нужен символ (и bigint), эквивалентный type O = { [k: string]: unknown } , поэтому я могу представить фактический объект JS (что-то, что может иметь любой ключ) с системой типов. Я могу сузить это позже по мере необходимости, но базовым типом для объекта JS будет, по сути, { [k: string | bigint | symbol | number]: unknown } .

А, я думаю, что вижу точку зрения

export interface Environment<T> {
    [Default](tag: string): Intrinsic<T>;
    [Text]?(text: string): string;
    [tag: string]: Intrinsic<T>;
    // TODO: allow symbol index parameters when typescript gets its shit together
    // [tag: symbol]: Intrinsic<T>;
}

где Intrinsic<T> - это тип функции, и я хочу разрешить разработчикам определять свои собственные свойства символов в средах, похожих на строки, но постольку, поскольку вы можете добавить [Symbol.iterator] , [Symbol.species] или настраиваемые свойства символа для любого интерфейса, подпись индекса с символами неправильно ограничивает любые объекты, реализующие эти свойства.

Итак, вы говорите, что нельзя сделать тип значения индексации по символу более конкретным, чем any ? Можем ли мы каким-то образом использовать различие unique symbol vs symbol чтобы разрешить это? Например, можем ли мы сделать подпись индекса по умолчанию для обычных символов и позволить уникальным / хорошо известным символам переопределять тип индекса? Даже если бы это было небезопасно, было бы полезно иметь возможность произвольно получать / устанавливать свойства по индексам символов.

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

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


В любом случае текущее поведение несовместимо со стандартом ES, что неверно.

Еще одна ночная мысль о типах символов. Почему это не ошибка?

const foo = {
  [Symbol.iterator]: 1,
}

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

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

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

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

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

Помимо всего прочего, общий вариант использования - если вы хотите отображать значения на произвольные символы. Или для совместимости с нетипизированным кодом ES6.

Вот пример места, которое, я думаю, было бы полезно: https://github.com/choojs/nanobus/pull/40/files. На практике eventName s могут быть символами или строками, поэтому я хотел бы иметь возможность сказать

type EventsConfiguration = { [eventName: string | Symbol]: (...args: any[]) => void }

на первой линии.

Но я могу что-то не понимать, как мне это делать.

Простой вариант использования не обходится без боли:

type Dict<T> = {
    [key in PropertyKey]: T;
};

function dict<T>() {
    return Object.create(null) as Dict<T>;
}

const has: <T>(dict: Dict<T>, key: PropertyKey) => boolean = Function.prototype.call.bind(Object.prototype.hasOwnProperty);

function forEach<T>(dict: Dict<T>, callbackfn: (value: T, key: string | symbol, dict: Dict<T>) => void, thisArg?: any) {
    for (const key in dict)
        if (has(dict, key))
            callbackfn.call(thisArg, dict[key], key, dict);
    const symbols = Object.getOwnPropertySymbols(dict);
    for (let i = 0; i < symbols.length; i++) {
        const sym = symbols[i];
        callbackfn.call(thisArg, dict[sym], sym, dict); // err
    }
}

const d = dict<boolean>();
const sym = Symbol('sym');
const bi = 9007199254740991n;

d[1] = true;
d['x'] = true;
d[sym] = false; // definitely PITA
d[bi] = false; // another PITA

forEach(d, (value, key) => console.log(key, value));

Я также не понимаю, зачем здесь нужен прецедент.

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

Похоже, что на самом деле воображаемые варианты использования большинства людей не будут решены так легко, как они думают (см. Ответ @brainkim здесь https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574550587), или что они одинаково хорошо решаются с помощью свойств символов (https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574538121) или карт (https://github.com/microsoft/TypeScript/issues/1863 # issuecomment-572733050).

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

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

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

Это несовместимость с ES6

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

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

const Answerable = Symbol.for("Answerable");
declare global {
  interface KnownSymbols {
    [Answerable](): string  | number;
  }
}

interface MyObject {
  [name: symbol]: boolean;
}

const MySymbol = Symbol.for("MySymbol");
const obj: MyObject = {
  [MySymbol]: true,
};

obj[Answerable] = () => "42";

Объявляя дополнительные свойства в глобальном интерфейсе KnownSymbols , вы разрешаете индексировать все объекты по этому символу и ограничиваете значение свойства неопределенным / вашим типом значения. Это немедленно предоставит ценность, позволив машинописному тексту обеспечивать типизацию для хорошо известных символов, предоставляемых ES6. Добавление свойства Symbol.iterator к объекту, который не является функцией, возвращающей итератор, явно должно быть ошибкой, но в настоящее время это не так, как в машинописном тексте. И это значительно упростило бы добавление хорошо известных свойств символа к уже существующим объектам.

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

Позволит ли реализация этого предложения развиваться типам подписей индекса?

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

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

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

В основном это привело к дискуссии о том, как мы не можем отличить закрытые типы от открытых. В этом контексте «закрытый» тип будет типом с конечным набором ключей, значения которых не могут быть расширены. Ключи своего рода точного типа, если хотите. Между тем, «открытый» тип в этом контексте - это тип, который при подтипе открыт для добавления дополнительных ключей (которые, согласно нашим текущим правилам подтипа, сортировка всех типов в основном иногда, кроме типов с индексными подписями, которые очень явно являются) . Индексные подписи подразумевают создание открытого типа, в то время как отображаемые типы во многом связаны, как если бы они оперировали закрытыми типами. Это _ обычно_ работает достаточно хорошо, потому что на практике большая часть кода написана со структурой, совместимой с закрытыми типами объектов. Вот почему flow (который имеет явный синтаксис для типов закрытых и открытых объектов) по умолчанию используется для закрытых типов объектов. Это особенно важно при использовании общих индексных ключей; Если у меня есть T extends string , поскольку T представляет собой экземпляры более широких и более широких типов (от "a" до "a" | "b" до string ), объект created становится все более и более специализированным, вплоть до того момента, пока мы не переключимся с "a" | "b" | ... (every other possible string) на сам string . Как только это происходит, тип внезапно становится очень открытым, и хотя каждое свойство потенциально может существовать для доступа, становится законным, например, назначать ему пустой объект. Это то, что происходит структурно, но когда мы связываем универсальные типы в сопоставленных типах, мы игнорируем это - ограничение string на общий ключ сопоставленного типа по существу связано, как будто оно делает все возможные ключи существующими. Это логически следует из простого представления типа ключа на основе дисперсии, но правильно только в том случае, если ключи происходят от типа _closed_ (который, кстати, тип с подписью индекса никогда не бывает закрытым!). Итак, если мы хотим быть обратно совместимыми, мы _не можем_ рассматривать {[x: T]: U} же, как {[_ in T]: U} , кроме случаев, когда мы этого хотим, поскольку в неуниверсальном случае {[_ in T]: U} становится {[x: T]: U} , отрегулируйте то, как мы обрабатываем дисперсию ключей сопоставленного типа, чтобы правильно учитывать «край» открытого типа, что само по себе является интересным изменением, которое может иметь разветвления в экосистеме.

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

Индивидуальные варианты использования не имеют значения.

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

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

Вы просите нас летать вслепую. Пожалуйста, не надо! Скажите, пожалуйста, куда бы вы хотели полететь!

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

Так что, если я правильно понимаю, в основном речь идет о следующей проблеме:

const sym = Symbol();
interface Foo
{
    [sym]: number;
    [s: symbol]: string; // just imagine this would be allowed
}

Теперь компилятор Typescript будет рассматривать это как конфликт, потому что Foo[sym] имеет амбивалентный тип. У нас уже есть такая же проблема со строками.

interface Foo
{
    ['str']: number; // <-- compiler error: not assignable to string index type 'string'
    [s: string]: string;
}

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

Я предполагаю, что для символов это будет вездесущей проблемой, потому что ECMA2015 определяет стандартные символы, такие как Symbol.iterator , которые могут использоваться для любого объекта и, следовательно, должны иметь типизацию по умолчанию. Кого у них, как ни странно, по-видимому, нет. По крайней мере, игровая площадка не позволяет мне запустить пример Symbol.iterator из MDN .

Предполагая, что планируется добавить предопределенные типы символов, это всегда приведет к тому, что общее определение [s: symbol]: SomeType будет недействительным, потому что предопределенные индексы символов уже имеют несовместимые типы, поэтому не может существовать общий общий тип или, возможно, потребуется быть типом function , потому что большинство (/ все?) предопределенных символьных ключей имеют тип function .

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

const foo: Foo = {str: 42, a: 'one', b: 'two'};
const input: string = getUserInput();
const value = foo[input];

Та же проблема применима к символьным клавишам. Во время компиляции невозможно определить точный тип value . Если пользователь вводит 'str' , это будет number , в противном случае это будет string (по крайней мере, Typescript ожидает, что это будет string , тогда как это скорее всего может стать undefined ). Это причина, по которой у нас нет этой функции? Это можно было бы обойти, задав value тип объединения, содержащий все возможные типы из определения (в данном случае number | string ).

@Neonit Ну, это не проблема, которая остановила прогресс в реализации, но это как раз одна из проблем, на которые я пытаюсь указать - в зависимости от того, что вы пытаетесь сделать, индексаторы символов могут не быть ответом.

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

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

Как насчет чего-то вроде типа UserSymbol который представляет собой всего лишь symbol минус встроенные символы? Сама природа символов гарантирует, что случайных столкновений не будет.

Изменить: если подумать об этом, хорошо известные символы - это просто часовые, которые реализованы с использованием Symbol . За исключением случаев, когда целью является сериализация или интроспекция объекта, код, вероятно, должен рассматривать эти индикаторы отдельно от других символов, поскольку они имеют особое значение для языка. Удаление их из типа symbol , вероятно, сделает (большую часть) кода, использующего «общие» символы, более безопасным.

@RyanCavanaugh вот мой план полета.

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

const X = Symbol.for(":ns/name")

const txMap = {
  [X]: "fly away with me!"
}

transact(txMap) // what's the index signature here?

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

// please forgive my tardiness but in essence this is how I'm typing "TxMap" for objects
type TxMapNs = { [ns: string]: TxMapLocal }
type TxMapLocal = { [name: string]: string | TxMapNs } // leaf or non leaf

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

interface TxMap = {
  [DB_IDENT]: symbol // leaf
  [DB_VALUE_TYPE]?: TxMap // not leaf
  [DB_CARDINALITY]?: TxMap
}

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


Я должен добавить, что это немного больно.

const x = Symbol.for(":x");
const y = Symbol.for(":x");

type X = { [x]: string };
type Y = { [y]: string };

const a: X = { [x]: "foo" };
const b: Y = { [x]: "foo" }; // not legal
const c: X = { [y]: "foo" }; // not legal
const d: Y = { [y]: "foo" };

Было бы здорово, если бы TypeScript мог понять, что символы, созданные с помощью функции Symbol.for самом деле одинаковы.


Это тоже очень раздражает.

function keyword(ns: string, name: string): unique symbol { // not possible, why?
  return Symbol.for(":" + ns + "/" + name)
}

const x: unique symbol = keyword("db", "id") // not possible, why?

type X = {
  [x]: string // not possible, why?
}

Эта небольшая служебная функция позволила мне применить соглашение к моей глобальной таблице символов. однако я не могу вернуть unique symbol , даже если он создан с помощью функции Symbol.for . Из-за того, как TypeScript все делает, он заставляет меня отказываться от определенных решений. Они просто не работают. И я думаю, это печально.

Я столкнулся с другим вариантом использования, когда symbol в качестве значения индексации было бы полезно при работе с ES Proxies для создания фабричной функции, которая обертывает объект с помощью прокси.

Вот пример:

let original = {
    foo: 'a',
    bar: 'b',
    baz: 1
};

function makeProxy<T extends Object>(source: T) {
    return new Proxy(source, {
        get: function (target, prop, receiver) {
            return target[prop];
        }
    });
}

let proxied = makeProxy(original);

Чтобы соответствовать сигнатуре типа ProxyConstructor универсальный аргумент должен расширять Object , но это приводит к ошибкам, поскольку общий аргумент не имеет ключа. Итак, мы можем расширить подпись типа:

function makeProxy<T extends Object & { [key: string]: any}>(source: T) {

Но теперь это вызовет ошибку, потому что второй аргумент ( prop ) get на ProxyHandler имеет тип PropertyKey который, как оказалось, имеет вид PropertyKey .

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

@aaronpowell С какой проблемой вы столкнулись? Я вижу, он ведет себя нормально:

let original = {
    foo: 'a',
    bar: 'b',
    baz: 1
};

function makeProxy<T extends Object>(source: T) {
    return new Proxy(source, {
        get: function (target, prop, receiver) {
            return target[prop];
        }
    });
}

let proxied = makeProxy(original);

function assertString(s:string){}
function assertNumber(x:number){}

assertString(proxied.foo); // no problem as string
assertNumber(proxied.baz); // no problem as number
console.log(proxied.foobar); // fails as expected: error TS2339: Property 'foobar' does not exist on type '{ foo: string; bar: string; baz: number; }'.

tsconfig.json:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "target": "es2015"
  }

package.json:

{
  "devDependencies": {
    "typescript": "~3.4.5"
  }
}

@beenotung Я вижу ошибку на детской площадке:

image

@aaronpowell ошибка появляется, когда вы включаете флаг 'strict' в 'compilerOptions' в tsconfig.json .

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

Конечно, но приведение any не совсем идеально, и отключение строгого режима просто ослабляет ограничения в безопасности типов.

Читая сообщения, я полагаю, что следующим «решением» будет, вероятно, «отключить машинописный текст».

Нам не нужно искать временные решения или объяснять, зачем нам это нужно.

Это стандартная функция javascript, поэтому она нужна нам в машинописном тексте.

@DanielRosenwasser мой варианту использования @aaronpowell - кажущееся несоответствие в интерфейсе ProxyHandler и правилах TypeScript мешает мне правильно вводить ловушки обработчика прокси.

Сводный пример, демонстрирующий проблему:

const getValue = (target: object, prop: PropertyKey) => target[prop]; // Error

Насколько я могу судить, невозможно создать какой-либо тип для target , который предотвратил бы ошибку, но разрешил бы только объекты, к которым может законно получить доступ PropertyKey .

Я новичок в TypeScript, поэтому, пожалуйста, простите меня, если я упустил что-то очевидное.

Другой вариант использования: я пытаюсь создать тип {[tag: symbol]: SomeSpecificType} для вызывающих, чтобы предоставить карту помеченных значений определенного типа таким образом, чтобы извлечь выгоду из компактности синтаксиса объектного литерала (при этом избегая конфликта имен риски использования простых строк в качестве тегов).

Другой вариант использования: я пытаюсь перебрать все перечисляемые свойства объекта, символов и строк. Мой текущий код выглядит примерно так (имена скрыты):

type ContextKeyMap = Record<PropertyKey, ContextKeyValue>

function setFromObject(context: Context, object: ContextKeyMap) {
    for (const key in object) {
        if (hasOwn.call(object, key)) context.setKey(key, object[key])
    }

    for (const symbol of Object.getOwnPropertySymbols(object)) {
        if (propertyIsEnumerable.call(object, symbol)) {
            context.setKey(symbol, object[symbol as unknown as string])
        }
    }
}

Я бы очень предпочел иметь возможность просто сделать это:

type ContextKeyMap = Record<PropertyKey, ContextKeyValue>

function setFromObject(context: Context, object: ContextKeyMap) {
    for (const key in object) {
        if (hasOwn.call(object, key)) context.setKey(key, object[key])
    }

    for (const symbol of Object.getOwnPropertySymbols(object)) {
        if (propertyIsEnumerable.call(object, symbol)) {
            context.setKey(symbol, object[symbol])
        }
    }
}

У меня также проблема с индексированием с помощью символов. Мой код выглядит следующим образом:

const cacheProp = Symbol.for('[memoize]')

function ensureCache<T extends any>(target: T, reset = false): { [key in keyof T]?: Map<any, any> } {
  if (reset || !target[cacheProp]) {
    Object.defineProperty(target, cacheProp, {
      value: Object.create(null),
      configurable: true,
    })
  }
  return target[cacheProp]
}

Я следил за решением @aaronpowell и каким-то образом смог его обойти

const cacheProp = Symbol.for('[memoize]') as any

function ensureCache<T extends Object & { [key: string]: any}>(target: T, reset = false): { [key in keyof T]?: Map<any, any> } {
  if (reset || !target[cacheProp]) {
    Object.defineProperty(target, cacheProp, {
      value: Object.create(null),
      configurable: true,
    })
  }

  return target[cacheProp]
}

Преобразование в any из symbol действительно не так хорошо.

Очень признателен за любые другие решения.

@ahnpnl Для этого WeakMap чем символы, и движки будут оптимизировать это лучше - это не изменяет карту типов target . Возможно, вам все равно придется его разыграть, но ваше приведение будет жить в возвращаемом значении.

Обходной путь - использовать универсальную функцию для присвоения значения ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Обходной путь - использовать универсальную функцию для присвоения значения ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Я не согласен. Эти три строки равны друг другу:

Object.assign(obj, {theAnswer: 42});
Object.assign(obj, {'theAnswer': 42});
obj['theAnswer'] = 42;

@DanielRosenwasser
У меня есть этот вариант использования. В ссылке на игровую площадку я также решил его с помощью карт, но посмотрите, это уродливо.

const system = Symbol('system');
const SomeSytePlugin = Symbol('SomeSytePlugin')

/** I would prefer to have this working in TS */
interface Plugs {
    [key: symbol]: (...args: any) => unknown;
}
const plugins = {
    "user": {} as Plugs,
    [system]: {} as Plugs
}
plugins[system][SomeSytePlugin] = () => console.log('awsome')
plugins[system][SomeSytePlugin](); ....

Ссылка на игровую площадку

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

Если у вас есть альтернативное решение, работающее с TS и имеющее такую ​​же читабельность кода, я все слышу.

Любое официальное объяснение этой проблемы?

Обходной путь - использовать универсальную функцию для присвоения значения ...

var theAnswer: symbol = Symbol('secret');
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Currently error, but should be allowed

Object.assign(obj, {theAnswer: 42}) // allowed

Вы ищете

Objet.assign(obj, { [theAnswer]: 42 });

Однако нет способа прочитать x[theAnswer] обратно без приведения AFAIK см. Комментарий два ниже

Ради любви к Богу, сделайте это своим приоритетом.

Вы ищете

Objet.assign(obj, { [theAnswer]: 42 });

Однако нет способа прочитать x[theAnswer] обратно без приведения AFAIK

Как указали Меллонис и Мингвэй Самуэль, обходные пути с использованием универсальной функции:

var theAnswer: symbol = Symbol("secret");
var obj = {} as Record<symbol, number>;

obj[theAnswer] = 42; // Not allowed, but should be allowed

Object.assign(obj, { [theAnswer]: 42 }); // allowed

function get<T, K extends keyof T>(object: T, key: K): T[K] {
  return object[key];
}

var value = obj[theAnswer]; // Not allowed, but should be allowed

var value = get(obj, theAnswer); // allowed

Пять лет и символ в качестве индекса все еще не разрешены

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

const SYMKEY = Symbol.for('my-key');

interface MyObject {   // Original object interface
  key: string
}

interface MyObjectExtended extends MyObject {
  [SYMKEY]?: string
}

const myObj: MyObject = {
  'key': 'value'
}

// myObj[SYMKEY] = '???' // Not allowed

function getValue(obj: MyObjectExtended, key: keyof MyObjectExtended): any {
  return obj[key];
}

function setValue(obj: MyObjectExtended, key: keyof MyObjectExtended, value: any): void {
  obj[key] = value
}

setValue(myObj, SYMKEY, 'Hello world');
console.log(getValue(myObj, SYMKEY));

@ james4388 Чем ваш пример отличается от примера @beenotung?

К вашему сведению: https://github.com/microsoft/TypeScript/pull/26797

(Только что нашел - на самом деле я не являюсь частью команды TS.)

Была ли эта страница полезной?
0 / 5 - 0 рейтинги