Typescript: Autoriser l'indexation avec des symboles

Créé le 30 janv. 2015  ·  93Commentaires  ·  Source: microsoft/TypeScript

TypeScript a maintenant un mode cible ES6 qui inclut des définitions Symbol . Cependant, lorsque j'essaye d'indexer un objet avec un symbole, j'obtiens une erreur (un argument d'expression d'index doit être de type 'string', 'number' ou 'any').

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

Commentaire le plus utile

Typescript 3.0.1, a été mordu par cela.
Je veux un enregistrement qui accepte symbol mais TS ne me le permettra pas.

Cela fait 3,5 ans que ce numéro a été ouvert, pouvons-nous avoir des symboles maintenant, s'il vous plaît 🙏

L'ironie est que TS se contredit.
TS développe keyof any = number | string | symbol .

Mais alors quand tu fais record[symbol] TS refuse de dire
_Le type 'symbole' ne peut pas être utilisé comme indexeur_.

Tous les 93 commentaires

Cela fait partie du support ES6 Symbol sur lequel @JsonFreeman travaille. Votre exemple de code devrait être pris en charge dans la prochaine version.

@wereHamster , avec pull request # 1978, cela devrait devenir légal, et obj[theAnswer] aura le type any . Est-ce suffisant pour ce que vous recherchez ou avez-vous besoin d'une frappe plus forte?

Sera-t-il possible de spécifier le type de propriétés qui sont indexées par des symboles? Quelque chose comme ce qui suit:

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

Sur la base des commentaires de ce PR, non:

_ Cela ne couvre pas les indexeurs de symboles, qui permettent à un objet d'agir comme une carte avec des clés de symboles arbitraires.

Je pense que @wereHamster parle d'un typage plus fort que @danquirk. Il existe 3 niveaux de support ici. Le niveau le plus basique est fourni par mon PR, mais ce n'est que pour les symboles qui sont les propriétés de l'objet Symbol global, pas pour les symboles définis par l'utilisateur. Alors,

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

Le prochain niveau de support serait d'autoriser un indexeur de symboles:

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

Ceci est sur notre radar et peut être mis en œuvre facilement.

Le niveau le plus fort est ce que vous demandez, c'est-à-dire quelque chose comme:

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

Ce serait vraiment bien, mais jusqu'à présent, nous n'avons pas proposé de design sensé. Cela semble finalement dépendre de la dépendance du type à la valeur d'exécution de ces symboles. Nous continuerons d'y réfléchir, car c'est clairement une chose utile à faire.

Avec mon PR, vous devriez au moins pouvoir utiliser un symbole pour extraire une valeur _out_ d'un objet. Ce sera any , mais vous n'obtiendrez plus d'erreur.

@wereHamster J'ai fait un petit article # 2012 qui pourrait vous intéresser.

J'ai fusionné la requête # 1978, mais je vais laisser ce bogue ouvert, car il semble demander plus que ce que j'ai fourni avec ce changement. Cependant, avec mon changement, l'erreur d'origine disparaîtra.

@wereHamster pouvez-vous publier une mise à jour de ce que vous aimeriez voir de plus se produire ici? Ce que nous avons mis en œuvre par rapport à ce que vous avez publié n'était pas immédiatement clair pour moi

Une idée quand symbol sera un type valide comme indexeur? Est-ce quelque chose qui pourrait être fait en tant que PR communautaire?

Nous prendrions un PR pour cela. @JsonFreeman peut fournir des détails sur certains des problèmes que vous pourriez rencontrer.

Je pense en fait que l'ajout d'un indexeur de symboles serait assez simple. Cela fonctionnerait exactement comme un nombre et une chaîne, sauf que cela ne serait pas compatible avec l'un ou l'autre dans l'assignabilité, l'inférence d'argument de type, etc.

@RyanCavanaugh , ce serait bien d'avoir éventuellement le dernier exemple dans https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456 typecheck. Mais si vous préférez, vous pouvez diviser ce problème en plusieurs problèmes plus petits qui se superposent.

Y a-t-il eu une mise à jour sur ce front? AFAIU la dernière version du compilateur prend en charge uniquement le premier niveau décrit dans https://github.com/Microsoft/TypeScript/issues/1863#issuecomment -73668456.

Nous serions heureux d'accepter les PR pour ce changement.

Il pourrait être utile de suivre les deux niveaux comme deux problèmes distincts. Les indexeurs semblent assez simples, mais l'utilité n'est pas claire. Le support complet avec suivi constant semble assez difficile, mais probablement plus utile.

Le suivi constant est déjà suivi dans https://github.com/Microsoft/TypeScript/issues/5579. ce problème concerne l'ajout de la prise en charge d'un indexeur de symboles, similaire aux indexeurs de chaînes et numériques.

Compris, ça a du sens.

@JsonFreeman @mhegazy un numéro est disponible au # 12932

Je pensais juste que je jetterais mon cas d'utilisation dans le ring. J'écris un outil qui permet de décrire les requêtes en spécifiant des clés de texte brut pour la correspondance avec des propriétés d'objet arbitraires, et des symboles pour spécifier des opérateurs de correspondance. En utilisant des symboles pour des opérateurs bien connus, j'évite l'ambiguïté de faire correspondre un opérateur par rapport à un champ dont la clé est la même que celle de l'opérateur bien connu.

Étant donné que les symboles ne peuvent pas être spécifiés comme clés d'index, contrairement à ce que JavaScript autorise explicitement, je suis obligé de convertir en <any> à plusieurs endroits, ce qui dégrade la qualité du code.

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

L'utilisation de premiers caractères "improbables" tels qu'un caractère $ n'est pas un compromis approprié, étant donné la variété des données que le moteur de requête peut avoir besoin d'inspecter.

Salut les gars. Y a-t-il un mouvement là-dedans? J'en ai besoin, je serais donc heureux d'apporter les changements nécessaires. Je n'ai pas encore contribué à TS.

@mhegazy @RyanCavanaugh Je sais que vous êtes incroyablement occupés, mais pourriez-vous peser quand vous en aurez l'occasion? Les symboles sont un outil vraiment important pour l'architecture des bibliothèques et des frameworks, et le manque de capacité à les utiliser dans les interfaces est un problème certain.

Je demande s'il y a quelque chose en cours? J'espère sincèrement que cette fonctionnalité sera prise en charge.

Ouais toujours à la recherche de ça aujourd'hui, c'est ce que je vois dans Webstorm:

screenshot 2017-10-08 21 37 17

Cela fonctionne réellement

var test: symbol = Symbol();

const x = {
    [test]: 1
};

x[test];

console.log(x[test]);

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

mais le type de x n'est pas correct, étant inféré comme

{
  [key: string]: number
}

Ouais toujours à la recherche de ça aujourd'hui, c'est ce que je vois dans Webstorm:

Veuillez noter que le service de langue de JetBrains est activé par défaut dans WebStorm, intelliJ IDEA, etc.

Cela fonctionne dans TS 2.7

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

Une mise à jour pour ceci?

Mon problème:

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

Mais cela ne fonctionne pas non plus, car le symbole ne peut pas être un type d'index.

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

Comportement prévisible:
symbol doit être un type d'index valide

Comportement réel:
symbol n'est pas un type d'index valide

Utiliser la solution de contournement avec le casting as string | number me semble très mauvais.

Comment util.promisify.custom censé être utilisé dans TypeScript? Il semble que l'utilisation de symboles constants soit désormais prise en charge, mais uniquement s'ils sont définis explicitement. Donc, c'est du TypeScript valide (à part f n'est pas initialisé):
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()
Mais si promisify.custom est utilisé à la place de custom , la tentative de référence f[promisify.custom] aboutit à l'erreur 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()
Je voudrais attribuer au champ promisify.custom d'une fonction, mais il semble (étant donné le comportement décrit ci-dessus) que le seul moyen de le faire est de convertir la fonction en un type any .

Je ne comprends pas pourquoi le symbole n'est pas autorisé en tant qu'index de clé, le code ci-dessous devrait fonctionner et est accepté par Typescript 2.8 mais il n'est pas autorisé par 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 = {};
  }
}

J'ai essayé ci-dessous, ce qui est plus `` correct '' pour moi mais ce n'est pas accepté par les deux versions du compilateur 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 = {};
  }
}

Le statut de ce ticket indique que ce que vous suggérez est le comportement souhaité, mais l'équipe principale n'engage pas à ce stade de ressources pour ajouter cette amélioration de fonctionnalité, elle l'ouvre à la communauté.

@beenotung Bien que ce ne soit pas une solution idéale, en supposant que la classe que vous avez publiée est le seul endroit où vous avez besoin d'un tel comportement, vous pouvez effectuer des conversions non sécurisées à l'intérieur de la classe, mais en gardant les signatures de classe et de la classe ne verra pas ça:

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

Étant donné que les signatures sont les mêmes, chaque fois que vous utilisez cette classe, la validation de type est appliquée correctement et, lorsque ce problème est résolu, il vous suffit de modifier cette classe (elle sera transparente pour les consommateurs).

Un exemple de consommateur est comme ci-dessous (le code n'aura pas besoin de changement lorsque ce problème sera résolu):

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

Typescript 3.0.1, a été mordu par cela.
Je veux un enregistrement qui accepte symbol mais TS ne me le permettra pas.

Cela fait 3,5 ans que ce numéro a été ouvert, pouvons-nous avoir des symboles maintenant, s'il vous plaît 🙏

L'ironie est que TS se contredit.
TS développe keyof any = number | string | symbol .

Mais alors quand tu fais record[symbol] TS refuse de dire
_Le type 'symbole' ne peut pas être utilisé comme indexeur_.

Oui, je souffre de celui-ci depuis un moment, malheureusement, ma dernière question à ce sujet:

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

@RyanCavanaugh @DanielRosenwasser @mhegazy Des mises à jour? Ce numéro approche de son quatrième anniversaire.

Si quelqu'un peut me diriger dans la bonne direction, je peux essayer. S'il y a des tests qui correspondent, c'est encore mieux.

@jhpratt il y a un PR en hausse au # 26797 (notez la mise en garde concernant les symboles bien connus). Il y a des notes de réunion de conception récentes à ce sujet dans # 28581 (mais aucune résolution n'y est enregistrée). Il y a un peu plus de commentaires sur les raisons pour lesquelles ce PR est bloqué ici . Cela semble être considéré comme un problème marginal / à faible impact, alors peut-être que plus de votes positifs sur le PR pourraient aider à rehausser le profil de la question.

Merci @yortus. Je viens de demander à Ryan si le PR est toujours prévu pour 3.2, ce qui est indiqué par le jalon. J'espère que c'est le cas, et cela sera résolu!

Le PR pointé par @yortus semble un énorme changement,
Le correctif de ce bogue ne devrait-il pas être très mineur? par exemple, ajouter une instruction ou dans le contrôle de condition.
(Je n'ai pas encore localisé l'endroit où changer.)

solution temporaire ici https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -412287117, un peu moche mais fait le travail

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

un autre https://github.com/Microsoft/TypeScript/issues/24587#issuecomment -460650063, puisque les 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' }

Je suppose que l'un des pièges que j'ai remarqués en utilisant des symboles est que si vous avez un projet impliquant le module child_process, oui vous pouvez partager des types / enums / interfaces entre les deux processus mais jamais des symboles.

c'est vraiment génial d'avoir résolu ce problème, les symboles sont vraiment excellents pour suivre les objets sans polluer leurs clés et être obligés d'utiliser des cartes / ensembles, en plus de cela, les repères des dernières années montrent que l'accès aux symboles est tout aussi rapide que l'accès à la chaîne / touches numériques


Edit: Il s'avère que cette approche ne fonctionne qu'avec Record<X,Y> mais pas Interfaces . J'ai fini par utiliser // @ts-ignore pour le moment car il est toujours syntaxiquement correct et se compile toujours bien en JS comme il se doit.

Une chose à noter cependant est que lorsque vous utilisez // @ts-ignore sur des lignes impliquant des symboles, il est en fait possible (et aide) de spécifier manuellement le type de ce symbole. VSCode le reprend en quelque sorte.

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

Je ne sais pas, si quelqu'un a commencé quelque chose pour résoudre ce problème, mais sinon, je l'ai maintenant.

Mon temps est limité, et je n'ai aucune connaissance des sources Typescript. J'ai fait un fork (https://github.com/Neonit/TypeScript), mais pas encore de pull request, car je ne veux pas molester les développeurs avec des changements inachevés (?) Je demanderais à chacun de contribuer ce qu'il peut à ma fourchette. J'émettrai finalement un PR alors.

Jusqu'à présent, j'ai trouvé un moyen de corriger la restriction de type d'index d'interface. Je ne sais pas s'il y a plus. J'ai pu indexer un objet avec un symbole dans TS 3.4 sans aucun correctif. ( https://www.typescriptlang.org/play/#src = const% 20o% 20% 3D% 20% 7B% 7D% 3B% 0D% 0Aconst% 20s% 20% 3D% 20Symbole ('s')% 3B % 0D% 0A% 0D% 0Ao% 5Bs% 5D% 20% 3D% 20123% 3B)

Jetez un œil à mon commit pour voir ce que j'ai trouvé: https://github.com/Neonit/TypeScript-SymbolKeys/commit/11cb7c13c2494ff32cdec2d4f82673058c825dc3

Disparu:

  • Tests: je n'ai pas eu le temps d'examiner comment les tests dans TS sont organisés et structurés.
  • Localisation: le message de diagnostic n'a été mis à jour que pour la variante anglaise. Peut-être que les autres langues reçoivent toujours l'ancien message. Je ne sais pas. Je ne pouvais de toute façon fournir qu'une traduction en allemand.

J'espère que cela fera enfin démarrer les choses après des années d'attente.

le correctif semble bon. Est-ce que TypeScript dev peut y jeter un œil?

bonjour, des progrès pour cela?

Je viens d'ouvrir un fil SO à ce sujet: https://stackoverflow.com/questions/59118271/using-symbol-as-object-key-type-in-typescript

Pourquoi n'est-ce pas possible? symbol n'est-il pas number - alors pourquoi y a-t-il une différence?

Bonjour, des progrès pour cela?

CINQ ans se sont écoulés!

Vous n'allez pas croire combien de temps il a fallu au C ++ pour obtenir des fermetures 😲

lol juste, mais C ++ ne se vend pas comme un sur-ensemble d'un langage qui a des fermetures:

@ljharb continue de battre ce cheval, il se contracte toujours 😛

Pour ceux qui ciblent des environnements d'exécution plus récents, pourquoi ne pas utiliser un Map ? J'ai découvert de manière anecdotique que beaucoup de développeurs ne savent pas que les Map existent, donc je suis curieux de savoir s'il y a un autre scénario qui me manque.

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

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


Les cartes et les objets ont des cas d'utilisation différents.

@DanielRosenwasser des symboles bien connus sont utilisés comme protocoles; une clé Map de Symbol.match , par exemple, ne rendra pas l'objet semblable à RegExp (et tout objet peut vouloir une clé Symbol.iterable pour le rendre itérable sans avoir à utiliser explicitement TS intégré Types itérables).

Presque 5 ans (

S'il vous plaît, implémentez cette fonctionnalité, je ne peux pas écrire de code normalement.

Les participants peuvent-ils fournir des exemples concrets dans leurs cas d'utilisation?

Je ne comprends pas l'exemple de protocole et pourquoi ce n'est pas possible aujourd'hui.

Voici un exemple de 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]())

Voici un exemple de Mappable ou Functor (manque de constructeurs de type d'ordre supérieur mis à part):

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 il semble que vous map , disons, sur n'importe quel objet javascript (ou du moins, un objet dont le type permet à n'importe quel symbole d'y être ajouté), ce qui le rend ensuite mappable.

L'installation d'une propriété (symbole ou non) sur un objet après coup fait partie d'une demande de fonctionnalité différente (souvent appelée «propriétés expando» ou «types expando»).

En l'absence de cela, le type dont vous auriez besoin pour une signature d'index de symbole fournirait très peu en tant qu'utilisateur TypeScript, non? Si je comprends bien, le type devrait être quelque chose comme unknown ou juste any pour être quelque peu utile.

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

Dans le cas des protocoles, c'est généralement une fonction, mais bien sûr, cela pourrait être unknown .

Ce dont j'ai besoin est le symbole (et bigint) équivalent de type O = { [k: string]: unknown } , donc je peux représenter un objet JS réel (quelque chose qui peut avoir n'importe quel type de clé) avec le système de types. Je peux restreindre cela plus tard si nécessaire, mais le type de base pour un objet JS serait essentiellement { [k: string | bigint | symbol | number]: unknown } .

Ah je crois voir le point @DanielRosenwasser . J'ai actuellement du code avec une interface comme:

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> est un type de fonction, et je veux permettre aux développeurs de définir leurs propres propriétés de symbole sur des environnements similaires aux chaînes, mais dans la mesure où vous pouvez ajouter [Symbol.iterator] , [Symbol.species] ou propriétés de symbole personnalisées à n'importe quelle interface, la signature d'index avec des symboles restreindrait à tort les objets implémentant ces propriétés.

Donc, ce que vous dites, c'est que vous ne pouvez pas rendre le type d'indexation valeur par symbole plus spécifique que any ? Pourrions-nous en quelque sorte utiliser la distinction unique symbol vs symbol pour permettre cela? Par exemple, pourrions-nous faire de la signature d'index une valeur par défaut pour les symboles réguliers et permettre aux symboles uniques / connus de remplacer le type d'index? Même si ce n'était pas de type sécurisé, il serait utile de pouvoir obtenir / définir arbitrairement des propriétés par index de symboles.

L'alternative serait que les utilisateurs étendent eux-mêmes l'interface d'environnement avec leurs propriétés de symbole, mais cela ne fournit aucune sécurité de type supplémentaire dans la mesure où les utilisateurs peuvent taper le symbole comme n'importe quoi sur l'objet.

@DanielRosenwasser voici un vrai exemple de mon code de production. Un état réutilisé dans de nombreux endroits comme une carte et peut accepter la clé de l'atome (entité dominée). Actuellement, je dois ajouter la prise en charge des symboles, mais j'obtiens beaucoup d'erreurs:


Quoi qu'il en soit le comportement actuel est incompatible avec la norme ES qui est erronée.

Une autre pensée de fin de soirée concernant les types de symboles. Pourquoi n'est-ce pas une erreur?

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

JS s'attend à ce que toutes les propriétés Symbol.iterator soient une fonction qui renvoie un itérateur, et cet objet briserait beaucoup de code s'il était transmis à divers endroits. S'il existait un moyen de définir globalement les propriétés des symboles pour tous les objets, nous pourrions autoriser des signatures d'index de symboles spécifiques tout en autorisant les remplacements globaux. Ce serait typiquement sûr, non?

Je ne comprends pas non plus pourquoi un cas d'utilisation serait nécessaire ici. Il s'agit d'une incompatibilité ES6, qui ne devrait pas exister dans un langage enveloppant ES6.

Dans le passé, j'ai publié mes conclusions sur la façon dont cela pourrait être corrigé ici dans ce fil de discussion et si ce code ne manque pas de vérifications ou de fonctionnalités importantes, je doute qu'il soit plus long de l'intégrer dans la base de code que de poursuivre cette discussion.

Je n'ai tout simplement pas fait de pull request, car je ne connais pas le cadre de test ou les exigences de Typescript et parce que je ne sais pas si des modifications dans différents fichiers seraient nécessaires pour que cela fonctionne dans tous les cas.

Donc, avant de continuer à investir du temps pour lire et écrire ici, veuillez vérifier si l'ajout de la fonctionnalité prendrait moins de temps. Je doute que quiconque se plaindrait de ce qu'il soit dans Typescript.

En dehors de tout cela, le cas d'utilisation général est si vous souhaitez mapper des valeurs sur des symboles arbitraires. Ou pour la compatibilité avec le code ES6 non typé.

Voici un exemple d'endroit que je pense que cela serait utile: https://github.com/choojs/nanobus/pull/40/files. En pratique, les eventName s peuvent être des symboles ou des chaînes, alors j'aimerais pouvoir dire

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

sur la première ligne.

Mais je pourrais mal comprendre quelque chose sur la façon dont je devrais faire cela.

Un cas d'utilisation simple ne peut pas être fait sans douleur:

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

Je ne comprends pas non plus pourquoi un cas d'utilisation serait nécessaire ici.

@neonit, il existe des PR pour résoudre ce problème, mais je crois comprendre qu'il existe des problèmes subtils avec la manière dont la fonctionnalité interagit avec le reste du système de type. En l'absence de solutions à cela, la raison pour laquelle je demande des cas d'utilisation est que nous ne pouvons pas simplement travailler / nous concentrer sur chaque fonctionnalité à la fois que nous aimerions - les cas d'utilisation doivent donc justifier le travail effectué, y compris le long terme maintenance d'une fonctionnalité.

Il semble qu'en fait, les cas d'utilisation imaginés par la plupart des gens ne seront pas résolus aussi facilement qu'ils l'imaginent (voir la réponse de @brainkim ici https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574550587), ou qu'ils sont aussi bien résolus via les propriétés des symboles (https://github.com/microsoft/TypeScript/issues/1863#issuecomment-574538121) ou Maps (https://github.com/microsoft/TypeScript/issues/1863 # issueecomment-572733050).

Je pense que @ Tyler-Murphy a donné le meilleur exemple ici en ce sens que vous ne pouvez pas écrire de contraintes, ce qui peut être très utile pour quelque chose comme un émetteur d'événements de type sécurisé qui prend en charge les symboles.

Donc, avant de continuer à investir du temps pour lire et écrire ici, veuillez vérifier si l'ajout de la fonctionnalité prendrait moins de temps. Je doute que quiconque se plaindrait de ce qu'il soit dans Typescript.

C'est toujours plus facile à dire lorsque vous n'avez pas à maintenir le projet! 😄 Je comprends que c'est quelque chose d'utile pour vous, mais j'espère que vous respectez cela.

Ceci est une incompatibilité ES6

Il existe de nombreuses constructions que TypeScript ne peut pas taper facilement car ce serait irréalisable. Je ne dis pas que c'est impossible, mais je ne pense pas que ce soit une manière appropriée de cadrer ce problème.

Il semble donc que l'impossibilité d'ajouter des clés de symboles comme signatures d'index provienne du fait qu'il existe des symboles mondiaux bien connus qui nécessitent leurs propres typages, avec lesquels les types d'index de symboles entreraient inévitablement en conflit. Comme solution, que se passerait-il si nous avions un module / interface global qui représentait tous les symboles connus?

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

En déclarant des propriétés supplémentaires sur l'interface globale KnownSymbols , vous autorisez tous les objets à être indexés par ce symbole et limitez la valeur de la propriété à undefined / votre type de valeur. Cela fournirait immédiatement de la valeur en permettant au typecript de fournir des typages pour les symboles bien connus fournis par ES6. Ajouter une propriété Symbol.iterator à un objet qui n'est pas une fonction qui retourne un itérateur devrait clairement être une erreur, mais ce n'est pas une erreur actuellement en écriture manuscrite. Et cela faciliterait beaucoup l'ajout de propriétés de symboles bien connues à des objets déjà existants.

Cette utilisation d'un module global permettrait également aux Symboles d'être également utilisés comme clés arbitraires, et donc dans les signatures d'index. Vous donneriez simplement aux propriétés de symbole connues globales la priorité sur le type de signature d'index local.

La mise en œuvre de cette proposition permettrait-elle aux types de signature d'index de progresser?

Les cas d'utilisation individuels ne sont pas pertinents. S'il s'agit de JavaScript cromulent, il doit être exprimable dans les définitions TS.

mais je crois comprendre qu'il y a des problèmes subtils avec la façon dont la fonctionnalité interagit avec le reste du système de type

Plus comme "refactorise la façon dont les signatures d'index fonctionnent entièrement en interne, c'est un grand changement effrayant et soulève des questions cromulet sur la façon dont les signatures d'index sont ou devraient différer des types mappés qui n'utilisent pas la variable modèle" pour être précis.

Cela a principalement conduit à une discussion sur la façon dont nous ne reconnaissons pas les types fermés par rapport aux types ouverts. Dans ce contexte, un type "fermé" serait un type avec un ensemble fini de clés dont les valeurs ne peuvent pas être étendues. Les clés d'une sorte de type exact, si vous voulez. Pendant ce temps, un type "ouvert" dans ce contexte est un type qui, lorsqu'il est sous-typé, est ouvert à l'ajout de plus de clés (ce qui, selon nos règles de sous-typage actuelles, sorta tous les types le plus souvent, sauf les types avec des signatures d'index qui le sont très explicitement) . Les signatures d'index impliquent de créer un type ouvert, tandis que les types mappés sont largement liés comme s'ils fonctionnaient sur des types fermés. Cela fonctionne généralement assez bien car la plupart du code, en pratique, est écrit avec une structure compatible avec les types d'objets fermés. C'est pourquoi flow (qui a une syntaxe explicite pour les types d'objets fermés et ouverts) utilise par défaut les types d'objets fermés. Cela vient à une tête avec des clés d'index génériques; Si j'ai un T extends string , comme T est instancié des types plus larges et plus larges (de "a" à "a" | "b" à string ), l'objet produit est de plus en plus spécialisé, jusqu'à ce que nous passions de "a" | "b" | ... (every other possible string) à string lui-même. Une fois que cela se produit, tout à coup le type est très ouvert, et bien que chaque propriété puisse potentiellement exister pour y accéder, il devient légal, par exemple, de lui attribuer un objet vide. C'est ce qui se passe structurellement, mais lorsque nous relions les génériques dans les types mappés, nous ignorons que - une contrainte string sur une clé de type mappé générique est essentiellement liée comme si elle permettait à toutes les clés possibles d'exister. Cela découle logiquement d'une simple vue basée sur la variance du type de clé, mais ce n'est que correct que les clés proviennent d'un type _closed_ (qui, ofc, un type avec une signature d'index n'est jamais réellement fermé!). Donc, si nous voulons être rétrocompatibles, nous _peut_ traiter {[x: T]: U} la même manière que {[_ in T]: U} , sauf si, ofc, nous le voulons, puisque dans le cas non générique {[_ in T]: U} devient {[x: T]: U} , ajustez la façon dont nous gérons la variance des clés de type mappées pour tenir compte correctement du type ouvert "edge", ce qui est un changement intéressant en soi qui pourrait avoir des ramifications sur l'écosystème.

À peu près: parce que cela rapproche beaucoup plus les types mappés et les signatures d'index, cela a soulevé un tas de questions sur la façon dont nous les gérons tous les deux, auxquelles nous n'avons pas encore de réponses satisfaisantes ou concluantes.

Les cas d'utilisation individuels ne sont pas pertinents.

C'est, poliment, pure folie. Comment savons-nous si nous ajoutons ou non une fonctionnalité avec le comportement que les gens veulent sans cas d'utilisation par lesquels juger ce comportement?

Nous n'essayons pas d'être difficiles ici en posant ces questions; nous essayons littéralement de nous assurer de mettre en œuvre les choses que les gens demandent. Ce serait vraiment dommage si nous implémentions quelque chose que nous pensions être "l'indexation avec des symboles", pour que les mêmes personnes dans ce fil reviennent et disent que nous l'avons fait totalement mal parce que cela ne répondait pas à leurs cas d'utilisation particuliers.

Vous nous demandez de voler à l'aveugle. Veuillez ne pas! Veuillez nous indiquer où vous souhaitez que l'avion aille!

Mon mal, j'aurais pu être plus clair sur ce que je voulais dire; il m'a semblé que les gens sentaient qu'ils devaient justifier leurs cas d'utilisation réels du code, plutôt que leur désir de le décrire plus précisément via TS

Donc, si je comprends bien, il s'agit principalement du problème suivant:

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

Maintenant, le compilateur Typescript verrait cela comme un conflit, car Foo[sym] a un type ambivalent. Nous avons déjà le même problème avec les chaînes.

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

La façon dont cela est géré avec les indices de chaîne est que les indices de chaîne spécifiques ne sont tout simplement pas autorisés, s'il existe une spécification générale pour les clés de chaîne et que leur type est incompatible.

Je suppose que pour les symboles, ce serait un problème omniprésent, car ECMA2015 définit des symboles standard comme Symbol.iterator , qui peuvent être utilisés sur n'importe quel objet et devraient donc avoir une saisie par défaut. Ce qu'ils n'ont curieusement pas apparemment. Au moins, le terrain de jeu ne me permet pas d'exécuter l'exemple Symbol.iterator de MDN .

En supposant qu'il est prévu d'ajouter des types de symboles prédéfinis, cela conduirait toujours à une définition générale [s: symbol]: SomeType invalide, car les index de symboles prédéfinis ont déjà des types incompatibles, il ne peut donc pas exister de type général commun ou peut-être qu'il faudrait être de type function , car la plupart (/ toutes?) des clés de symboles prédéfinis sont de type function .

Un problème avec le mélange de types d'index généraux et spécifiques est la détermination du type lorsque l'objet est indexé avec une valeur inconnue au moment de la compilation. Imaginez que mon exemple ci-dessus avec les indices de chaîne soit valide, alors ce qui suit serait possible:

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

Le même problème s'appliquerait aux clés de symboles. Il est impossible de déterminer le type exact de value au moment de la compilation. Si l'utilisateur saisit 'str' , ce serait number , sinon ce serait string (au moins Typescript s'attendrait à ce que ce soit un string , alors qu'il peut devenir undefined ). Est-ce la raison pour laquelle nous n'avons pas cette fonctionnalité? On peut contourner ce problème en donnant à value un type d'union contenant tous les types possibles de la définition (dans ce cas number | string ).

@Neonit Eh bien, ce n'est pas le problème qui a freiné la progression d'une implémentation, mais c'est exactement l'un des problèmes que j'essaie de souligner - que selon ce que vous essayez de faire, les indexeurs de symboles pourraient ne pas être la réponse.

Si cette fonctionnalité était implémentée, les symboles intégrés d'ECMAScript ne ruineraient pas nécessairement tout car tous les types n'utilisent pas ces symboles; mais tout type qui ne définit une propriété avec un symbole bien connu (ou tout autre symbole que vous vous définissez) serait probablement limitée à une signature d'index moins utile pour les symboles.

C'est vraiment la chose à garder à l'esprit - les cas d'utilisation «Je veux utiliser ceci comme carte» et «Je veux utiliser des symboles pour implémenter des protocoles» sont incompatibles du point de vue du système de type. Donc, si vous aviez quelque chose de ce genre à l'esprit, les signatures d'index de symboles pourraient ne pas vous aider, et vous pourriez être mieux servi via des propriétés de symbole explicites ou des cartes.

Qu'en est-il de quelque chose comme un type UserSymbol qui n'est que symbol moins les symboles intégrés? La nature même des symboles garantit qu'il n'y aura jamais de collisions accidentelles .

Edit: En y réfléchissant davantage, les symboles bien connus ne sont que des sentinelles qui sont implémentées en utilisant Symbol . À moins que l'objectif ne soit la sérialisation ou l'introspection d'objets, le code devrait probablement traiter ces sentinelles différemment des autres symboles, car ils ont une signification particulière pour le langage. Les supprimer du type symbol rendra probablement (la plupart) le code utilisant des symboles «génériques» plus sûr.

@RyanCavanaugh voici mon plan de vol.

J'ai un système dans lequel j'utilise des symboles comme celui-ci pour les propriétés.

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

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

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

Dans ce cas, je veux que txMap corresponde à la signature de type transact . Mais à ma connaissance, je ne peux pas l'exprimer aujourd'hui. Dans mon cas, transact fait partie d'une bibliothèque qui ne sait pas à quoi s'attendre. Je fais quelque chose comme ça pour les propriétés.

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

Je peux générer l'ensemble des types qui correspondent à transact partir du schéma et l'utiliser. Pour cela, je ferais quelque chose comme ça et je compterais sur la fusion des déclarations.

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

Mais ce serait bien si je pouvais au moins revenir à une signature d'index pour les symboles, je m'attends seulement à ce que transact reçoive des objets JavaScript simples, j'utilise également uniquement les symboles du registre de symboles global dans ce cas. Je n'utilise pas de symboles privés.


Je dois ajouter que c'est un peu pénible.

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

Ce serait super si TypeScript pouvait comprendre que les symboles créés via la fonction Symbol.for sont en fait les mêmes.


C'est aussi super ennuyeux.

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

Cette petite fonction utilitaire me permet d'appliquer une convention sur ma table de symboles globale. cependant, je ne peux pas renvoyer un unique symbol , même s'il est créé via la fonction Symbol.for . En raison de la façon dont TypeScript fait les choses, cela m'oblige à renoncer à certaines solutions. Ils ne fonctionnent tout simplement pas. Et je pense que c'est triste.

J'ai rencontré un autre cas d'utilisation où symbol tant que valeur d'indexation serait utile, lorsque vous travaillez avec ES Proxies pour créer une fonction d'usine qui enveloppe un objet avec un proxy.

Prenons cet exemple:

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

Pour faire correspondre la signature de type ProxyConstructor l'argument générique doit étendre Object , mais cela entraîne des erreurs car l'argument générique n'est pas indexé. Nous pouvons donc étendre la signature de type:

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

Mais maintenant, cela va générer une erreur car le 2ème argument ( prop ) de get sur ProxyHandler est de type PropertyKey qui se trouve être PropertyKey .

Je ne sais donc pas comment faire cela avec TypeScript en raison des restrictions de ce problème.

@aaronpowell Quel est le problème auquel vous faites face? Je vois qu'il se comporte bien:

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 Je vois une erreur dans le terrain de jeu:

image

@aaronpowell l'erreur apparaît lorsque vous activez l'indicateur 'strict' dans les 'compilerOptions' dans tsconfig.json .

Donc, dans la version actuelle du compilateur dactylographié, vous devez soit désactiver le mode strict, soit convertir la cible en any ...

Bien sûr, mais un cast any n'est pas vraiment idéal et la désactivation du mode strict revient simplement à desserrer les restrictions de sécurité de type.

Lecture des messages j'imagine que la prochaine "solution" sera probablement de "désactiver le typographie".

Nous ne devrions pas avoir à chercher des solutions provisoires ni expliquer pourquoi nous en avons besoin.

C'est une fonctionnalité standard de javascript, nous en avons donc besoin en écriture manuscrite.

@DanielRosenwasser mon cas d'utilisation est similaire à celui de @aaronpowell - une incompatibilité apparente dans l'interface ProxyHandler et les règles de TypeScript m'empêchant de taper correctement les traps de gestionnaire de proxy.

Un exemple résumé illustrant le problème:

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

Autant que je sache, il est impossible de créer un type pour target qui évitera l'erreur tout en n'autorisant que les objets qui peuvent être légitimement accessibles par PropertyKey .

Je suis un débutant de TypeScript alors pardonnez-moi s'il me manque quelque chose d'évident.

Un autre cas d'utilisation: j'essaie d'avoir un type {[tag: symbol]: SomeSpecificType} pour que les appelants fournissent une carte des valeurs étiquetées d'un type spécifique d'une manière qui bénéficie de la compacité de la syntaxe littérale d'objet (tout en évitant le conflit de nom risques liés à l'utilisation de chaînes simples comme balises).

Un autre cas d'utilisation: j'essaie d'itérer toutes les propriétés énumérables d'un objet, les symboles et les chaînes à la fois. Mon code actuel ressemble à ceci (noms masqués):

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

Je préférerais très fortement pouvoir faire ceci:

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

J'ai également des problèmes avec l'indexation des symboles. Mon code est le suivant:

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

J'ai suivi la solution par @aaronpowell et

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

Diffuser vers any partir de symbol n'est pas si agréable en effet.

Vraiment apprécié pour toutes autres solutions.

@ahnpnl Pour ce cas d'utilisation, vous feriez mieux d'utiliser un WeakMap que des symboles, et les moteurs optimiseraient cela mieux - cela ne modifie pas la carte de type de target . Vous devrez peut-être encore le lancer, mais votre distribution vivra dans la valeur de retour.

Une solution de contournement consiste à utiliser une fonction générique pour attribuer une valeur ...

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

Une solution de contournement consiste à utiliser une fonction générique pour attribuer une valeur ...

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

Je ne suis pas d'accord. Ces trois lignes sont égales l'une à l'autre:

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

@DanielRosenwasser
J'ai ce cas d'utilisation, dans le lien du terrain de jeu, je l'ai également résolu en utilisant des cartes, mais jetez un coup d'œil, c'est moche.

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

Lien Playground

L'utilisation de symboles ici exclut l'éventuel écrasement accidentel qui se produit lors de l'utilisation de chaînes. Cela rend l'ensemble du système plus robuste et plus facile à entretenir.

Si vous avez une solution alternative qui fonctionne avec TS et qui a la même lisibilité dans le code, je suis toute oreille.

Un fonctionnaire explique-t-il ce problème?

Une solution de contournement consiste à utiliser une fonction générique pour attribuer une valeur ...

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

Vous cherchez

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

Cependant, il n'y a pas de moyen de lire x[theAnswer] sans un casting AFAIK voir le commentaire deux ci-dessous

Pour l'amour de Dieu, veuillez en faire une priorité.

Vous cherchez

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

Cependant, il n'y a pas de moyen de lire x[theAnswer] sans un casting AFAIK

Comme indiqué par mellonis et MingweiSamuel, les solutions de contournement utilisant la fonction générique sont:

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

Cinq ans et le symbole comme index n'est toujours pas autorisé

Trouvé une solution de contournement sur ce cas, ce n'est pas générique mais fonctionne dans certains cas:

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 En quoi votre exemple est-il différent de celui de @beenotung?

Pour info: https://github.com/microsoft/TypeScript/pull/26797

(Je viens de le trouver - je ne fais pas partie de l'équipe TS.)

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

fdecampredon picture fdecampredon  ·  358Commentaires

OliverJAsh picture OliverJAsh  ·  242Commentaires

quantuminformation picture quantuminformation  ·  273Commentaires

blakeembrey picture blakeembrey  ·  171Commentaires

yortus picture yortus  ·  157Commentaires