Typescript: Indizierung mit Symbolen zulassen

Erstellt am 30. Jan. 2015  ·  93Kommentare  ·  Quelle: microsoft/TypeScript

TypeScript verfügt jetzt über einen ES6-Zielmodus, der Definitionen Symbol . Beim Versuch, ein Objekt mit einem Symbol zu indizieren, wird jedoch eine Fehlermeldung angezeigt (Ein Indexausdrucksargument muss vom Typ 'Zeichenfolge', 'Zahl' oder 'Beliebig' sein).

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

Hilfreichster Kommentar

Typoskript 3.0.1, wurde davon gebissen.
Ich möchte einen Datensatz, der symbol akzeptiert, aber TS lässt mich nicht.

Es ist 3,5 Jahre her, seit diese Ausgabe eröffnet wurde. Können wir jetzt bitte Symbole haben?

Die Ironie ist, dass TS sich selbst widerspricht.
TS erweitert keyof any = number | string | symbol .

Aber wenn Sie record[symbol] TS tun, weigert sich das zu sagen
Typ 'Symbol' kann nicht als Indexer verwendet werden.

Alle 93 Kommentare

Dies ist Teil der ES6- @JsonFreeman arbeitet. Ihr Codebeispiel sollte in der nächsten Version unterstützt werden.

@wereHamster , mit Pull-Anfrage # 1978 sollte dies legal werden, und obj[theAnswer] hat den Typ any . Reicht das für das aus, wonach Sie suchen, oder benötigen Sie eine stärkere Eingabe?

Wird es möglich sein, die Art der Eigenschaften anzugeben, die durch Symbole indiziert werden? So etwas wie das Folgende:

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

Basierend auf den Kommentaren in dieser PR, nein:

_Dies gilt nicht für Symbolindexer, wodurch ein Objekt als Karte mit beliebigen Symbolschlüsseln fungieren kann._

Ich denke, @wereHamster spricht von einer stärkeren Eingabe als @danquirk. Hier gibt es 3 Unterstützungsstufen. Die grundlegendste Ebene wird von meiner PR bereitgestellt, dies gilt jedoch nur für Symbole, die Eigenschaften des globalen Symbolobjekts sind, nicht für benutzerdefinierte Symbole. Damit,

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

Die nächste Unterstützungsstufe wäre, einen Symbolindexer zuzulassen:

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

Dies ist auf unserem Radar und kann einfach implementiert werden.

Das stärkste Level ist das, wonach du fragst, was ungefähr so ​​aussieht:

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

Das wäre wirklich schön, aber wir haben uns bisher kein vernünftiges Design dafür ausgedacht. Letztendlich scheint es davon abhängig zu sein, dass der Typ vom Laufzeitwert dieser Symbole abhängt. Wir werden weiter darüber nachdenken, da dies eindeutig eine nützliche Sache ist.

Mit meiner PR sollten Sie zumindest in der Lage sein, ein Symbol zu verwenden, um einen Wert _out_ eines Objekts abzurufen. Es wird any , aber Sie werden keinen Fehler mehr erhalten.

@wereHamster Ich habe ein wenig # 2012 geschrieben, an dem Sie interessiert sein könnten.

Ich habe die Anfrage Nr. 1978 zusammengeführt, aber ich werde diesen Fehler offen lassen, da er anscheinend mehr verlangt, als ich mit dieser Änderung bereitgestellt habe. Mit meiner Änderung wird jedoch der ursprüngliche Fehler behoben.

@wereHamster Kannst du ein Update veröffentlichen, was mehr hier passieren soll? Mir war nicht sofort klar, was wir implementiert haben und was Sie gepostet haben

Irgendeine Idee, wann symbol als Indexer gültig sein wird? Ist dies etwas, das als Community-PR gemacht werden könnte?

Wir würden dafür eine PR machen. @JsonFreeman kann Details zu einigen Problemen bereitstellen, auf die Sie möglicherweise

Ich denke tatsächlich, dass das Hinzufügen eines Symbolindexers ziemlich einfach wäre. Es würde genauso funktionieren wie Zahl und Zeichenfolge, außer dass es in Bezug auf Zuweisbarkeit, Typargumentinferenz usw. nicht mit beiden kompatibel wäre. Die größte Herausforderung besteht darin, sicherzustellen, dass Sie daran denken, an allen geeigneten Stellen Logik hinzuzufügen.

@ RyanCavanaugh , es wäre schön, irgendwann das letzte Beispiel in https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456 typecheck zu haben. Wenn Sie es vorziehen, können Sie dieses Problem in mehrere kleinere Probleme aufteilen, die aufeinander aufbauen.

Gab es ein Update an dieser Front? AFAIU, die neueste Version des Compilers, unterstützt nur die erste unter https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456 beschriebene Ebene.

Wir würden uns freuen, PRs für diese Änderung zu akzeptieren.

Es könnte sich lohnen, die beiden Ebenen als zwei separate Probleme zu verfolgen. Die Indexer scheinen ziemlich einfach zu sein, aber der Nutzen ist nicht klar. Die volle Unterstützung mit ständiger Verfolgung scheint ziemlich schwierig, aber wahrscheinlich nützlicher zu sein.

Die ständige Verfolgung wird bereits unter https://github.com/Microsoft/TypeScript/issues/5579 verfolgt. Dieses Problem dient zum Hinzufügen von Unterstützung für einen Symbolindexer, ähnlich wie String- und numerische Indexer.

Verstanden, macht Sinn.

@JsonFreeman @mhegazy Ein Problem ist unter # 12932 verfügbar

Ich dachte nur, ich würde meinen Anwendungsfall in den Ring werfen. Ich schreibe ein Tool, mit dem Abfragen beschrieben werden können, indem Klartextschlüssel zum Abgleichen mit beliebigen Objekteigenschaften und Symbole zum Angeben übereinstimmender Operatoren angegeben werden. Durch die Verwendung von Symbolen für bekannte Operatoren vermeide ich die Mehrdeutigkeit, einen Operator mit einem Feld abzugleichen, dessen Schlüssel mit dem des bekannten Operators identisch ist.

Da Symbole nicht als Indexschlüssel angegeben werden können, muss ich im Gegensatz zu dem, was JavaScript ausdrücklich zulässt, an mehreren Stellen in <any> umwandeln, was die Codequalität beeinträchtigt.

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

Die Verwendung von "unwahrscheinlichen" ersten Zeichen wie einem $ -Zeichen ist angesichts der Vielzahl von Daten, die die Abfrage-Engine möglicherweise überprüfen muss, kein geeigneter Kompromiss.

Hallo Leute. Gibt es eine Bewegung darin? Ich brauche es, damit ich gerne die notwendigen Änderungen beitragen kann. Ich habe noch nie zu TS beigetragen.

@mhegazy @RyanCavanaugh Ich weiß, ihr seid unglaublich beschäftigt, aber

Ich frage, ob etwas in Arbeit ist? Ich hoffe aufrichtig, dass diese Funktion unterstützt wird.

Ja, ich suche heute noch danach, das sehe ich in Webstorm:

screenshot 2017-10-08 21 37 17

Das funktioniert tatsächlich

var test: symbol = Symbol();

const x = {
    [test]: 1
};

x[test];

console.log(x[test]);

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

aber die Art von x ist nicht richtig und wird als abgeleitet

{
  [key: string]: number
}

Ja, ich suche heute noch danach, das sehe ich in Webstorm:

Bitte beachten Sie, dass der JetBrains-eigene Sprachdienst standardmäßig in WebStorm, IntelliJ IDEA usw. aktiviert ist.

Dies funktioniert in TS 2.7

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

Gibt es hierzu Neuigkeiten?

Mein Problem:

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>'
}

Dies funktioniert aber auch nicht, da das Symbol kein Indextyp sein kann.

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

Erwartetes Verhalten:
symbol sollte ein gültiger Indextyp sein

Tatsächliches Verhalten:
symbol ist kein gültiger Indextyp

Die Problemumgehung beim Casting von as string | number scheint mir sehr schlecht.

Wie soll util.promisify.custom in TypeScript verwendet werden? Es scheint, dass die Verwendung konstanter Symbole jetzt unterstützt wird, jedoch nur, wenn sie explizit definiert sind. Dies ist also gültiges TypeScript (abgesehen davon, dass f nicht initialisiert wird):
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()
Wenn jedoch promisify.custom anstelle von custom , führt der Versuch, auf f[promisify.custom] zu verweisen, zum Fehler 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()
Ich möchte dem promisify.custom -Feld einer Funktion zuweisen, aber es scheint (angesichts des oben beschriebenen Verhaltens), dass die einzige Möglichkeit, dies zu tun, darin besteht, die Funktion in einen any -Typ umzuwandeln.

Ich kann nicht verstehen, warum das Symbol als Schlüsselindex nicht zulässig ist. Der folgende Code sollte funktionieren und wird von Typescript 2.8 akzeptiert, ist jedoch von Typescript 2.9 nicht zulässig

/**
 * 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 = {};
  }
}

Ich habe es unten versucht, was für mich "korrekter" ist, aber von beiden Versionen des Typescript-Compilers nicht akzeptiert wird

/**
 * 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 = {};
  }
}

Der Status dieses Tickets zeigt an, dass das von Ihnen vorgeschlagene Verhalten das gewünschte Verhalten ist. Das Kernteam stellt jedoch derzeit keine Ressourcen bereit, um diese Funktionserweiterung hinzuzufügen. Es öffnet es für die Community, um es zu adressieren.

@beenotung Obwohl dies keine ideale Lösung ist, können Sie unter der Annahme, dass die von Ihnen veröffentlichte Klasse der einzige Ort ist, an dem Sie ein solches Verhalten benötigen, unsichere Casts innerhalb der Klasse wobei die Signaturen der Klasse und der Methoden jedoch gleich bleiben , damit die Verbraucher der Klasse wird das nicht sehen:

/**
 * 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 = {};
  }
}

Da die Signaturen gleich sind, wird bei jeder Verwendung dieser Klasse die Typüberprüfung korrekt angewendet. Wenn dieses Problem behoben ist, müssen Sie nur diese Klasse ändern (sie ist für die Verbraucher transparent).

Ein Beispiel für einen Verbraucher ist wie folgt (der Code muss nicht geändert werden, wenn dieses Problem behoben ist):

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

Typoskript 3.0.1, wurde davon gebissen.
Ich möchte einen Datensatz, der symbol akzeptiert, aber TS lässt mich nicht.

Es ist 3,5 Jahre her, seit diese Ausgabe eröffnet wurde. Können wir jetzt bitte Symbole haben?

Die Ironie ist, dass TS sich selbst widerspricht.
TS erweitert keyof any = number | string | symbol .

Aber wenn Sie record[symbol] TS tun, weigert sich das zu sagen
Typ 'Symbol' kann nicht als Indexer verwendet werden.

Ja, ich habe leider eine Weile mit diesem Problem gelitten, meine letzte Frage zu diesem Thema:

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

@RyanCavanaugh @DanielRosenwasser @mhegazy Irgendwelche Updates? Diese Ausgabe nähert sich ihrem vierten Geburtstag.

Wenn mich jemand in die richtige Richtung weisen könnte, könnte ich es versuchen. Wenn es passende Tests gibt, noch besser.

@jhpratt gibt es eine PR bei # 26797 (beachten Sie die Einschränkung über bekannte Symbole). Es gibt aktuelle Notizen zu Designmeetings in # 28581 (aber keine Auflösung dort aufgezeichnet). Es ist ein bisschen mehr Feedback darüber , warum die PR gehalten ist hier . Es scheint als Randproblem mit geringen Auswirkungen angesehen zu werden, daher können möglicherweise mehr positive Stimmen für die PR dazu beitragen, das Profil des Problems zu verbessern.

Danke @yortus. Ich habe Ryan gerade gefragt, ob die PR noch für 3.2 geplant ist, was durch den Meilenstein angezeigt wird. Hoffentlich ist das der Fall und das wird gelöst!

Die PR, auf die @yortus hinweist, scheint eine große Veränderung zu sein.
Sollte die Behebung dieses Fehlers nicht sehr geringfügig sein? zB Hinzufügen einer oder Anweisung in der Bedingungsprüfung.
(Ich habe den Ort zum Umziehen noch nicht gefunden.)

temporäre Lösung hier https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -412287117, ein bisschen hässlich, aber erledigt den Job

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

ein weiteres https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -460650063, da Linters 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' }

Ich denke, eines der Probleme, die mir bei der Verwendung von Symbolen aufgefallen sind, ist, dass Sie, wenn Sie ein Projekt mit dem Modul child_process haben, Typen / Aufzählungen / Schnittstellen zwischen den beiden Prozessen teilen können, aber niemals Symbole.

Es ist wirklich großartig, dies gelöst zu haben. Symbole eignen sich hervorragend zum Verfolgen von Objekten, ohne ihre Schlüssel zu verschmutzen, und müssen Karten / Sets verwenden. Darüber hinaus zeigen die Benchmarks der letzten Jahre, dass der Zugriff auf Symbole genauso schnell ist wie der Zugriff auf Zeichenfolgen / Zifferntasten


Bearbeiten: Es stellt sich heraus, dass dieser Ansatz nur mit Record<X,Y> funktioniert, nicht aber mit Interfaces . Ich habe vorerst // @ts-ignore da es immer noch syntaktisch korrekt ist und sich immer noch gut in JS kompilieren lässt, so wie es sollte.

Beachten Sie jedoch, dass es bei Verwendung von // @ts-ignore in Zeilen mit Symbolen tatsächlich möglich (und hilfreich) ist, den Typ dieses Symbols manuell anzugeben. VSCode greift es immer noch auf.

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

Ich weiß nicht, ob jemand etwas begonnen hat, um dies zu beheben, aber wenn nicht, habe ich es jetzt.

Meine Zeit ist jedoch begrenzt und ich habe keine Kenntnisse über die Typescript-Quellen. Ich habe eine Gabelung (https://github.com/Neonit/TypeScript) erstellt, aber noch keine Pull-Anfrage, da ich die Entwickler nicht mit unvollendeten (?) Änderungen belästigen möchte. Ich würde jeden bitten, zu meiner Gabel beizutragen, was er kann. Ich werde dann irgendwann eine PR ausstellen.

Bisher habe ich einen Weg gefunden, um die Einschränkung des Schnittstellenindex-Typs zu beheben. Ich weiß nicht, ob mehr dahinter steckt. Ich konnte ein Objekt mit einem Symbol in TS 3.4 ohne Korrekturen indizieren. ( https://www.typescriptlang.org/play/#src = const% 20o% 20% 3D% 20% 7B% 7D% 3B% 0D% 0Aconst% 20s% 20% 3D% 20Symbol ('s')% 3B % 0D% 0A% 0D% 0Ao% 5Bs% 5D% 20% 3D% 20123% 3B)

Sehen Sie sich mein Commit an, um zu sehen, was ich gefunden habe: https://github.com/Neonit/TypeScript-SymbolKeys/commit/11cb7c13c2494ff32cdec2d4f82673058c825dc3

Vermisst:

  • Tests: Ich hatte keine Zeit zu untersuchen, wie Tests in TS organisiert und strukturiert sind.
  • Lokalisierung: Die Diagnosemeldung wurde nur für die englische Variante aktualisiert. Vielleicht bekommen die anderen Sprachen die alte Nachricht noch. Ich weiß es nicht. Ich konnte sowieso nur eine deutsche Übersetzung liefern.

Ich hoffe, dass dies nach Jahren des Wartens endlich losgeht.

Das Update sieht gut aus. Kann TypeScript dev einen Blick darauf werfen?

Hallo, irgendwelche Fortschritte dafür?

Ich habe gerade einen SO-Thread dazu geöffnet: https://stackoverflow.com/questions/59118271/using-symbol-as-object-key-type-in-typescript

Warum ist das nicht möglich? Ist symbol anderer primitiver Typ wie number - warum gibt es also einen Unterschied?

Hallo, irgendwelche Fortschritte dafür?

FÜNF Jahre sind vergangen!

Sie werden nicht glauben, wie lange C ++ gebraucht hat, um Schließungen zu erhalten 😲

lol fair, aber C ++ vermarktet sich nicht als Obermenge einer Sprache, die Verschlüsse hat :-p

@ljharb schlag das Pferd weiter, es zuckt immer noch 😛

Verwenden Sie für diejenigen, die auf neuere Laufzeiten abzielen, Map existieren, daher bin ich gespannt, ob mir ein anderes Szenario fehlt.

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

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


Karten und Objekte haben unterschiedliche Anwendungsfälle.

@DanielRosenwasser bekannte Symbole werden als Protokolle verwendet; Ein Map-Schlüssel von Symbol.match macht das Objekt beispielsweise nicht RegExp-ähnlich (und jedes Objekt möchte möglicherweise einen Symbol.iterable -Schlüssel, um es iterierbar zu machen, ohne explizit den integrierten TS verwenden zu müssen Iterierbare Typen).

Fast 5 Jahre (

Bitte implementieren Sie diese Funktion. Ich kann normalerweise keinen Code schreiben.

Können die Teilnehmer in ihren Anwendungsfällen konkrete Beispiele nennen?

Ich verstehe das Protokollbeispiel nicht und warum es heute nicht möglich ist.

Hier ist ein Beispiel für 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]())

Hier ist ein Beispiel für Mappable oder Functor (abgesehen von Konstruktoren vom Typ höherer Ordnung):

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 Anscheinend nehmen Sie an, dass alle Objekte eine Schnittstelle haben oder eine Klasseninstanz sind oder im Voraus bekannt sind. Anhand Ihres letzten Beispiels sollte es mir möglich sein, map auf einem beliebigen Javascript-Objekt (oder zumindest auf einem Objekt, dessen Typ das Hinzufügen eines Symbols ermöglicht) zu installieren, wodurch es dann zuordnungsfähig wird.

Das Installieren einer Eigenschaft (Symbol oder nicht) auf einem Objekt nachträglich ist Teil einer anderen Feature-Anforderung (häufig als "expando properties" oder "expando types" bezeichnet).

Andernfalls würde der Typ, den Sie für eine Symbolindexsignatur benötigen, als TypeScript-Benutzer nur sehr wenig bieten, oder? Wenn ich das richtig verstehe, muss der Typ so etwas wie unknown oder nur any , um etwas nützlich zu sein.

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

Bei Protokollen ist dies im Allgemeinen eine Funktion, aber sicher kann es sich um unknown .

Was ich brauche, ist das Symbol (und Bigint), das type O = { [k: string]: unknown } , damit ich ein tatsächliches JS-Objekt (etwas, das jede Art von Schlüssel haben kann) mit dem Typsystem darstellen kann. Ich kann das später nach Bedarf eingrenzen, aber der Basistyp für ein JS-Objekt wäre im Wesentlichen { [k: string | bigint | symbol | number]: unknown } .

Ah ich glaube ich sehe @DanielRosenwasser Punkt. Ich habe derzeit Code mit einer Schnittstelle wie:

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

Dabei ist Intrinsic<T> ein Funktionstyp, und ich möchte Entwicklern erlauben, ihre eigenen Symboleigenschaften in Umgebungen zu definieren, die Strings ähnlich sind, aber sofern Sie [Symbol.iterator] , [Symbol.species] oder hinzufügen können Benutzerdefinierte Symboleigenschaften für jede Schnittstelle. Die Indexsignatur mit Symbolen würde Objekte, die diese Eigenschaften implementieren, fälschlicherweise einschränken.

Sie sagen also, dass Sie den Werttyp der Indizierung nach Symbol nicht spezifischer als any festlegen können? Könnten wir irgendwie die Unterscheidung zwischen unique symbol und symbol , um dies zu ermöglichen? Wie könnten wir die Indexsignatur als Standard für reguläre Symbole festlegen und eindeutigen / bekannten Symbolen erlauben, den Indextyp zu überschreiben? Selbst wenn es nicht typsicher wäre, wäre es hilfreich, Eigenschaften durch Symbolindizes willkürlich abrufen / festlegen zu können.

Die Alternative wäre, dass Benutzer die Umgebungsschnittstelle selbst mit ihren Symboleigenschaften erweitern. Dies bietet jedoch keine zusätzliche Typensicherheit, da Benutzer das Symbol wie auf dem Objekt eingeben können.

@ DanielRosenwasser hier ein echtes Beispiel für meinen Produktionscode. Ein Staat wird an vielen Stellen als Karte wiederverwendet und akzeptiert möglicherweise den Atomschlüssel (domained feature). Derzeit muss ich Symbolunterstützung hinzufügen, aber ich erhalte viele Fehler:


Auf jeden Fall ist das aktuelle Verhalten nicht mit dem falschen ES-Standard kompatibel.

Ein weiterer nächtlicher Gedanke, den ich in Bezug auf Symboltypen hatte. Warum ist das kein Fehler?

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

JS erwartet, dass alle Symbol.iterator -Eigenschaften eine Funktion sind, die einen Iterator zurückgibt, und dieses Objekt würde viel Code beschädigen, wenn es an verschiedenen Stellen herumgereicht würde. Wenn es eine Möglichkeit gäbe, Symboleigenschaften für alle Objekte global zu definieren, könnten wir bestimmte Symbolindexsignaturen zulassen und gleichzeitig globale Überschreibungen zulassen. Es wäre typsicher, oder?

Ich verstehe auch nicht, warum hier ein Anwendungsfall benötigt wird. Dies ist eine ES6-Inkompatibilität, die in einer ES6-Sprache nicht vorhanden sein sollte.

In der Vergangenheit habe ich meine Ergebnisse darüber veröffentlicht, wie dies hier in diesem Thread behoben werden kann. Wenn diesem Code keine wichtigen Überprüfungen oder Funktionen fehlen, bezweifle ich, dass es zeitaufwändiger ist, ihn in die Codebasis zu integrieren, als diese Diskussion fortzusetzen.

Ich habe nur keine Pull-Anfrage gestellt, weil ich das Testframework oder die Anforderungen von Typescript nicht kenne und nicht weiß, ob Änderungen in verschiedenen Dateien erforderlich sind, damit dies in allen Fällen funktioniert.

Bevor Sie also weiterhin Zeit zum Lesen und Schreiben investieren, prüfen Sie bitte, ob das Hinzufügen der Funktion weniger zeitaufwändig ist. Ich bezweifle, dass sich jemand darüber beschweren würde, dass es sich um Typoskript handelt.

Abgesehen davon ist der allgemeine Anwendungsfall, wenn Sie Werte auf beliebige Symbole abbilden möchten. Oder zur Kompatibilität mit nicht typisiertem ES6-Code.

Hier ist ein Beispiel für einen Ort, den ich für hilfreich halte: https://github.com/choojs/nanobus/pull/40/files. In der Praxis können eventName s Symbole oder Zeichenfolgen sein, daher würde ich gerne sagen können

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

in der ersten Zeile.

Aber ich könnte etwas falsch verstehen, wie ich das tun sollte.

Ein einfacher Anwendungsfall kann nicht ohne Schmerzen durchgeführt werden:

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

Ich verstehe auch nicht, warum hier ein Anwendungsfall benötigt wird.

@neonit gibt es PRs, um dies zu Daher müssen Anwendungsfälle die Ausführung von Arbeiten rechtfertigen, die auch langfristige umfassen Wartung einer Funktion.

Es scheint, dass die imaginären Anwendungsfälle der meisten Menschen nicht so einfach gelöst werden können, wie sie es sich vorstellen (siehe @brainkims Antwort hier https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574550587). oder dass sie über Symboleigenschaften (https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574538121) oder Karten (https://github.com/microsoft/TypeScript/issues/1863) gleich gut gelöst werden # issuecomment-572733050).

Ich denke, @ Tyler-Murphy hat hier das beste Beispiel dafür gegeben, dass Sie keine Einschränkungen schreiben können, was für so etwas wie einen typsicheren Ereignisemitter, der Symbole unterstützt, sehr nützlich sein kann.

Bevor Sie also weiterhin Zeit zum Lesen und Schreiben investieren, prüfen Sie bitte, ob das Hinzufügen der Funktion weniger zeitaufwändig ist. Ich bezweifle, dass sich jemand darüber beschweren würde, dass es sich um Typoskript handelt.

Dies ist immer einfacher zu sagen, wenn Sie das Projekt nicht warten müssen! 😄 Ich verstehe, dass dies etwas Nützliches für Sie ist, aber ich hoffe, Sie respektieren das.

Dies ist eine ES6-Inkompatibilität

Es gibt viele Konstrukte, die TypeScript nicht einfach eingeben kann, da dies nicht möglich wäre. Ich sage nicht, dass dies unmöglich ist, aber ich glaube nicht, dass dies ein angemessener Weg ist, um dieses Problem zu lösen.

Es scheint also, dass die Unfähigkeit, Symbolschlüssel als Indexsignaturen hinzuzufügen, auf die Tatsache zurückzuführen ist, dass es weltweit bekannte Symbole gibt, die ihre eigenen Eingaben erfordern, mit denen Symbolindexarten unweigerlich in Konflikt geraten würden. Was wäre als Lösung, wenn wir ein globales Modul / eine globale Schnittstelle hätten, die alle bekannten Symbole darstellt?

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";

Indem Sie zusätzliche Eigenschaften auf der globalen Schnittstelle KnownSymbols deklarieren, können Sie zulassen, dass alle Objekte durch dieses Symbol indiziert werden, und den Wert der Eigenschaft auf undefiniert / Ihren Werttyp beschränken. Dies würde sofort einen Wert liefern, indem Typoskript Typisierungen für die bekannten Symbole von ES6 bereitstellen könnte. Das Hinzufügen einer Symbol.iterator -Eigenschaft zu einem Objekt, das keine Funktion ist, die einen Iterator zurückgibt, sollte eindeutig ein Fehler sein, ist jedoch derzeit kein Typoskript. Dies würde das Hinzufügen bekannter Symboleigenschaften zu bereits vorhandenen Objekten erheblich vereinfachen.

Diese Verwendung eines globalen Moduls würde es auch ermöglichen, Symbole auch als beliebige Schlüssel und daher in Indexsignaturen zu verwenden. Sie würden nur den global bekannten Symboleigenschaften Vorrang vor dem lokalen Indexsignaturtyp geben.

Würde die Umsetzung dieses Vorschlags es Indexsignaturtypen ermöglichen, voranzukommen?

Einzelne Anwendungsfälle sind irrelevant. Wenn es sich um cromulentes JavaScript handelt, muss es in TS-Definitionen ausgedrückt werden können.

Nach meinem Verständnis gibt es jedoch subtile Probleme bei der Interaktion der Funktion mit dem Rest des Typsystems

Eher wie "Refaktoren, wie Indexsignaturen intern vollständig funktionieren, ist eine beängstigende große Änderung und wirft Cromulet-Fragen auf, wie Indexsignaturen von zugeordneten Typen sind oder sein sollten, die die Vorlagenvariable nicht verwenden", um genau zu sein.

Dies führte hauptsächlich zu einer Diskussion darüber, wie wir geschlossene Typen gegenüber offenen Typen nicht erkennen können. In diesem Zusammenhang wäre ein "geschlossener" Typ ein Typ mit einem endlichen Satz von Schlüsseln, deren Werte nicht erweitert werden können. Die Schlüssel einer Art exakten Typs, wenn Sie so wollen. In der Zwischenzeit ist ein "offener" Typ in diesem Kontext ein Typ, der, wenn er subtypisiert ist, offen ist, weitere Schlüssel hinzuzufügen (was nach unseren aktuellen Subtypisierungsregeln meistens manchmal sortiert ist, mit Ausnahme von Typen mit Indexsignaturen, die sehr explizit sind). . Indexsignaturen bedeuten, dass ein offener Typ erstellt wird, während zugeordnete Typen weitgehend miteinander verbunden sind, als würden sie über geschlossenen Typen ausgeführt. Dies funktioniert normalerweise gut genug, da der meiste Code in der Praxis mit einer Struktur geschrieben wird, die mit geschlossenen Objekttypen kompatibel ist. Aus diesem Grund verwendet flow (mit expliziter Syntax für geschlossene und offene Objekttypen) standardmäßig geschlossene Objekttypen. Dies spitzt sich mit generischen Indexschlüsseln zu; Wenn ich ein T extends string , da T breitere und breitere Typen instanziiert werden (von "a" bis "a" | "b" bis string ), das Objekt Produziert wird immer spezialisierter, bis wir von "a" | "b" | ... (every other possible string) auf string selbst wechseln. Sobald dies geschieht, ist der Typ plötzlich sehr offen, und obwohl möglicherweise jede Eigenschaft für den Zugriff vorhanden ist, ist es legal, ihm beispielsweise ein leeres Objekt zuzuweisen. Das passiert strukturell, aber wenn wir die Generika in zugeordneten Typen in Beziehung setzen, ignorieren wir dies - eine string -Einschränkung für einen generischen zugeordneten Typschlüssel hängt im Wesentlichen so zusammen, als ob alle möglichen Schlüssel vorhanden wären. Dies ergibt sich logischerweise aus einer einfachen varianzbasierten Ansicht des Schlüsseltyps, ist jedoch nur dann korrekt, wenn die Schlüssel von einem geschlossenen Typ stammen (der natürlich ein Typ mit einer Indexsignatur niemals tatsächlich geschlossen wird!). Wenn wir also abwärtskompatibel sein wollen, können wir {[x: T]: U} genauso behandeln wie {[_ in T]: U} , es sei denn, wir möchten dies, da im nicht generischen Fall {[_ in T]: U} wird zu {[x: T]: U} . Passen Sie an, wie wir mit der Varianz der zugeordneten Typschlüssel umgehen, um die offene Typkante richtig zu berücksichtigen. Dies ist eine interessante Änderung für sich, die Auswirkungen auf das Ökosystem haben kann.

Ziemlich genau: Da dadurch zugeordnete Typen und Indexsignaturen viel näher zusammengebracht werden, wurden einige Fragen zum Umgang mit beiden aufgeworfen, auf die wir noch keine zufriedenstellenden oder schlüssigen Antworten haben.

Einzelne Anwendungsfälle sind irrelevant.

Das ist höflich purer Wahnsinn. Woher wissen wir, ob wir eine Funktion mit dem gewünschten Verhalten hinzufügen oder nicht, ohne Anwendungsfälle, anhand derer dieses Verhalten beurteilt werden kann?

Wir versuchen hier nicht schwierig zu sein, indem wir diese Fragen stellen. Wir versuchen buchstäblich sicherzustellen, dass wir die Dinge umsetzen, nach denen die Leute fragen. Es wäre eine echte Schande, wenn wir etwas implementieren würden, von dem wir dachten, es sei "Indizieren mit Symbolen", nur um die gleichen Personen in diesem Thread zurück zu kommen und zu sagen, dass wir es völlig falsch gemacht haben, weil es nicht auf ihre speziellen Anwendungsfälle eingegangen ist.

Sie bitten uns, blind zu fliegen. Bitte nicht! Bitte teilen Sie uns mit, wohin das Flugzeug fliegen soll!

Mein schlechtes, ich hätte klarer sagen können, was ich meinte; Es schien mir, dass die Leute das Gefühl hatten, ihre tatsächlichen Code-Anwendungsfälle rechtfertigen zu müssen, anstatt den Wunsch, sie über TS genauer zu beschreiben

Wenn ich es richtig verstehe, geht es hauptsächlich um das folgende Problem:

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

Jetzt würde der Typescript-Compiler dies als Konflikt betrachten, da Foo[sym] einen ambivalenten Typ hat. Wir haben bereits das gleiche Problem mit Zeichenfolgen.

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

Die Art und Weise, wie dies mit Zeichenfolgenindizes behandelt wird, besteht darin, dass bestimmte Zeichenfolgenindizes einfach nicht zulässig sind, wenn es eine allgemeine Spezifikation für Zeichenfolgenschlüssel gibt und deren Typ nicht kompatibel ist.

Ich denke, für Symbole wäre dies ein allgegenwärtiges Problem, da ECMA2015 Standardsymbole wie Symbol.iterator , die für jedes Objekt verwendet werden können und daher eine Standardeingabe haben sollten. Was sie anscheinend seltsamerweise nicht haben. Zumindest erlaubt mir der Spielplatz nicht, das Beispiel Symbol.iterator von MDN aus auszuführen .

Angenommen, es ist geplant, vordefinierte Symboltypen hinzuzufügen, würde dies immer dazu führen, dass eine allgemeine [s: symbol]: SomeType -Definition ungültig ist, da die vordefinierten Symbolindizes bereits inkompatible Typen haben, sodass es keinen gemeinsamen allgemeinen Typ geben kann oder muss Seien Sie ein function -Typ, da die meisten (/ alle?) vordefinierten Symbolschlüssel vom Typ function .

Ein Problem bei der Mischung aus allgemeinen und spezifischen Indextypen ist die Typbestimmung, wenn das Objekt mit einem Wert indiziert wird, der zur Kompilierungszeit nicht bekannt ist. Stellen Sie sich vor, mein obiges Beispiel mit den Zeichenfolgenindizes wäre gültig, dann wäre Folgendes möglich:

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

Das gleiche Problem würde für Symbolschlüssel gelten. Es ist unmöglich, den genauen Typ von value zur Kompilierungszeit zu bestimmen. Wenn der Benutzer 'str' , wäre dies number , andernfalls wäre es string (zumindest würde Typescript erwarten, dass es sich um string , während dies der Fall ist wahrscheinlich wird undefined ). Ist dies der Grund, warum wir diese Funktion nicht haben? Man könnte dies umgehen, indem man value einen Unionstyp gibt, der alle möglichen Typen aus der Definition enthält (in diesem Fall number | string ).

@Neonit Nun, das ist nicht das Problem, das den Fortschritt bei einer Implementierung gestoppt hat, aber genau das ist eines der Probleme, auf die ich hinweisen möchte - je nachdem, was Sie tun möchten, sind Symbolindexer möglicherweise nicht die Antwort.

Wenn diese Funktion implementiert wurde, ist ECMAScript integrierten Symbolen würde nicht unbedingt alles ruinieren , weil nicht jede Art diese Symbole verwendet; Jeder Typ, der eine Eigenschaft mit einem bekannten Symbol definiert (oder ein Symbol, das Sie selbst definieren), ist wahrscheinlich auf eine weniger nützliche Indexsignatur für Symbole beschränkt.

Das ist wirklich das, woran man denken sollte - die Anwendungsfälle "Ich möchte dies als Karte verwenden" und "Ich möchte Symbole verwenden, um Protokolle zu implementieren" sind aus Sicht des Typsystems nicht kompatibel. Wenn Sie also an so etwas gedacht haben, helfen Ihnen Symbolindex-Signaturen möglicherweise nicht weiter, und Sie werden möglicherweise über explizite Symboleigenschaften oder Karten besser bedient.

Was ist mit so etwas wie einem UserSymbol Typ, der nur symbol minus eingebaute Symbole ist? Die Natur der Symbole stellt sicher, dass es niemals zu versehentlichen Kollisionen kommt.

Bearbeiten: Wenn Sie mehr darüber nachdenken, sind bekannte Symbole nur Sentinels, die zufällig mit Symbol implementiert werden. Sofern das Ziel nicht die Serialisierung oder Selbstbeobachtung von Objekten ist, sollte Code diese Sentinels wahrscheinlich anders behandeln als andere Symbole, da sie für die Sprache eine besondere Bedeutung haben. Sie aus dem Entfernen von symbol Art wird wahrscheinlich machen ( die meisten) Code ‚Generika‘ Symbole sicherer werden.

@ RyanCavanaugh hier ist mein Flugplan.

Ich habe ein System, in dem ich solche Symbole für Eigenschaften verwende.

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

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

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

In diesem Fall möchte ich, dass txMap zur Typensignatur von transact passt. Aber meines Wissens kann ich das heute nicht ausdrücken. In meinem Fall ist transact Teil einer Bibliothek, die nicht weiß, was sie erwartet. Ich mache so etwas für Immobilien.

// 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

Ich kann die Menge der Typen, die zu transact passen, aus dem Schema generieren und diese verwenden. Dafür würde ich so etwas tun und mich auf das Zusammenführen von Deklarationen verlassen.

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

Aber es wäre schön, wenn ich zumindest auf eine Indexsignatur für Symbole zurückgreifen könnte. Ich erwarte nur, dass transact einfache JavaScript-Objekte übergeben werden. In diesem Fall verwende ich auch nur Symbole aus der globalen Symbolregistrierung. Ich benutze keine privaten Symbole.


Ich sollte hinzufügen, dass dies ein bisschen schmerzhaft ist.

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" };

Es wäre super, wenn TypeScript verstehen könnte, dass Symbole, die mit der Funktion Symbol.for , tatsächlich dieselben sind.


Das ist auch super nervig.

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?
}

Mit dieser kleinen Utility-Funktion kann ich eine Konvention für meine globale Symboltabelle erzwingen. Ich kann jedoch kein unique symbol , selbst wenn es über die Funktion Symbol.for . Aufgrund der Art und Weise, wie TypeScript Dinge tut, zwinge es mich, auf bestimmte Lösungen zu verzichten. Sie funktionieren einfach nicht. Und ich finde das traurig.

Ich bin auf einen anderen Anwendungsfall gestoßen , bei dem symbol als Indizierungswert nützlich wäre, wenn Sie mit ES Proxies arbeiten , um eine Factory-Funktion zu erstellen, die ein Objekt mit einem Proxy

Nehmen Sie dieses Beispiel:

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

Um die Signatur vom Object . Dies ist jedoch ein Fehler, da das generische Argument nicht verschlüsselt ist. So können wir die Typensignatur erweitern:

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

Aber jetzt wird ein Fehler ausgelöst, da das zweite Argument ( prop ) von get auf ProxyHandler vom Typ PropertyKey was zufällig PropertyKey .

Aufgrund der Einschränkungen dieses Problems bin ich mir nicht sicher, wie ich das mit TypeScript machen soll.

@aaronpowell Was ist das Problem, mit dem Sie konfrontiert sind? Ich sehe, es verhält sich gut:

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 Ich sehe einen Fehler auf dem Spielplatz:

image

@aaronpowell Der Fehler wird tsconfig.json aktivieren.

In der aktuellen Version des Typoskript-Compilers müssen Sie entweder den strengen Modus deaktivieren oder das Ziel in any ...

Sicher, aber ein any Cast ist nicht wirklich ideal und das Deaktivieren des strengen Modus lockert nur die Einschränkungen in der Typensicherheit.

Lesen von Nachrichten Ich stelle mir vor, dass die nächste "Lösung" wahrscheinlich darin besteht, "Typoskript zu deaktivieren".

Wir sollten nicht nach Notlösungen suchen und auch nicht erklären müssen, warum wir sie brauchen.

Es ist eine Standardfunktion von Javascript, daher benötigen wir es in Typoskript.

@DanielRosenwasser Mein Anwendungsfall ähnelt dem von @aaronpowell - eine scheinbare ProxyHandler -Schnittstelle und den TypeScript-Regeln, die mich daran hindern, Proxy-Handler-Traps richtig einzugeben.

Ein Beispiel, das das Problem demonstriert:

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

Soweit ich das beurteilen kann, ist es unmöglich, einen Typ für target erstellen, der den Fehler abwendet und dennoch nur Objekte zulässt, auf die PropertyKey legitim zugreifen kann.

Ich bin ein TypeScript-Neuling. Bitte verzeihen Sie mir, wenn mir etwas Offensichtliches fehlt.

Ein weiterer Anwendungsfall: Ich versuche, einen Typ {[tag: symbol]: SomeSpecificType} für Anrufer zu haben, um eine Karte mit markierten Werten eines bestimmten Typs bereitzustellen, die von der Kompaktheit der Objektliteral-Syntax profitiert (und dennoch den Namenskonflikt vermeidet) Risiken der Verwendung einfacher Zeichenfolgen als Tags).

Ein weiterer Anwendungsfall: Ich versuche, alle aufzählbaren Eigenschaften eines Objekts, Symbole und Zeichenfolgen zu iterieren. Mein aktueller Code sieht ungefähr so ​​aus (Namen verdeckt):

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])
        }
    }
}

Ich würde es sehr stark vorziehen, dies einfach tun zu können:

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])
        }
    }
}

Ich habe auch Probleme mit der Indizierung mit Symbolen. Mein Code lautet wie folgt:

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]
}

Ich folgte der Lösung von @aaronpowell und schaffte es irgendwie, sie zu

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]
}

Das Casting von symbol auf any symbol ist in der Tat nicht so schön.

Wirklich geschätzt für alle anderen Lösungen.

@ahnpnl Für diesen Anwendungsfall ist es besser, ein WeakMap als Symbole zu verwenden, und Engines würden dies besser optimieren - die Typzuordnung von target wird nicht geändert. Möglicherweise müssen Sie es noch wirken, aber Ihre Besetzung würde im Rückgabewert leben.

Eine Problemumgehung besteht darin, eine generische Funktion zu verwenden, um einen Wert zuzuweisen ...

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

Eine Problemumgehung besteht darin, eine generische Funktion zu verwenden, um einen Wert zuzuweisen ...

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

Ich stimme nicht zu. Diese drei Zeilen sind einander gleich:

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

@ DanielRosenwasser
Ich habe diesen Anwendungsfall. Im Spielplatz-Link habe ich ihn auch mithilfe von Karten gelöst, aber schauen Sie, es ist hässlich.

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](); ....

Spielplatz Link

Die Verwendung von Symbolen schließt hier das mögliche versehentliche Überschreiben aus, das bei der Verwendung von Zeichenfolgen auftritt. Dies macht das gesamte System robuster und wartungsfreundlicher.

Wenn Sie eine alternative Lösung haben, die mit TS funktioniert und die gleiche Lesbarkeit im Code aufweist, bin ich ganz Ohr.

Gibt es eine offizielle Erklärung für dieses Problem?

Eine Problemumgehung besteht darin, eine generische Funktion zu verwenden, um einen Wert zuzuweisen ...

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

Du schaust nach

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

Es gibt jedoch keine Möglichkeit, x[theAnswer] ohne Besetzung AFAIK wieder zu lesen ( siehe Kommentar 2 unten)

Aus Liebe zu Gott machen Sie dies bitte zu einer Priorität.

Du schaust nach

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

Es gibt jedoch keine Möglichkeit, x[theAnswer] ohne AFAIK-Besetzung wieder zu lesen

Wie von Mellonis und MingweiSamuel hervorgehoben, sind die Problemumgehungen mit der generischen Funktion:

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

Fünf Jahre und Symbol als Index noch nicht erlaubt

Es wurde eine Problemumgehung für diesen Fall gefunden, die nicht generisch ist, aber in einigen Fällen funktioniert:

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 Wie unterscheidet sich Ihr Beispiel von dem von @beenotung?

Zu Ihrer Information: https://github.com/microsoft/TypeScript/pull/26797

(Ich habe es gerade gefunden - ich bin eigentlich nicht Teil des TS-Teams.)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen