Typescript: Suggestion : autoriser les accesseurs get/set à être de différents types

Créé le 26 mars 2015  ·  125Commentaires  ·  Source: microsoft/TypeScript

Ce serait formidable s'il y avait un moyen d'assouplir la contrainte actuelle d'exiger que les accesseurs get/set aient le même type. ce serait utile dans une situation comme celle-ci:

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Actuellement, cela ne semble pas être possible, et je dois recourir à quelque chose comme ceci :

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

C'est loin d'être idéal, et le code serait beaucoup plus propre si différents types étaient autorisés.

Merci!

Design Limitation Suggestion Too Complex

Commentaire le plus utile

Un getter et un setter JavaScript avec différents types est parfaitement valide et fonctionne très bien et je pense que c'est le principal avantage/objectif de cette fonctionnalité. Devoir fournir un setMyDate() juste pour faire plaisir à TypeScript le ruine.

Pensez également aux bibliothèques JS pures qui suivront ce modèle : les .d.ts devront exposer une union ou any .

Le problème est que les accesseurs ne sont pas affichés dans .d.ts différemment des propriétés normales

Ensuite, cette limitation devrait être corrigée et ce problème devrait rester ouvert :

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Tous les 125 commentaires

Je peux voir comment ce serait bien ici (et cela a déjà été demandé bien que je ne puisse pas trouver le problème maintenant) mais il n'est pas possible de savoir si l'utilitaire est suffisant ou non pour le justifier. Le problème est que les accesseurs ne sont pas affichés dans .d.ts différemment des propriétés normales, car ils apparaissent identiques de ce point de vue. Cela signifie qu'il n'y a pas de différenciation entre le getter et le setter, il n'y a donc aucun moyen a) d'exiger qu'une implémentation utilise un accesseur plutôt qu'un seul membre d'instance et b) de spécifier la différence de types entre le getter et le setter.

Merci pour la réponse rapide, Dan. Je vais suivre la voie la moins élégante. Merci pour le bon travail!

Un getter et un setter JavaScript avec différents types est parfaitement valide et fonctionne très bien et je pense que c'est le principal avantage/objectif de cette fonctionnalité. Devoir fournir un setMyDate() juste pour faire plaisir à TypeScript le ruine.

Pensez également aux bibliothèques JS pures qui suivront ce modèle : les .d.ts devront exposer une union ou any .

Le problème est que les accesseurs ne sont pas affichés dans .d.ts différemment des propriétés normales

Ensuite, cette limitation devrait être corrigée et ce problème devrait rester ouvert :

// MyClass.d.ts

// Instead of generating:
declare class MyClass {
  myDate: moment.Moment;
}

// Should generate:
declare class MyClass {
  get myDate(): moment.Moment;
  set myDate(value: Date | moment.Moment);
}

// Or shorter syntax:
declare class MyClass {
  myDate: (get: moment.Moment, set: Date | moment.Moment);
  // and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
}

Je me rends compte que ce n'est qu'une opinion, mais écrire un setter tel que a.x === y n'est pas true immédiatement après a.x = y; est un drapeau rouge géant. Comment les consommateurs d'une bibliothèque comme celle-là savent-ils quelles propriétés ont des effets secondaires magiques et lesquelles n'en ont pas ?

Comment savez-vous quelles propriétés ont des effets secondaires magiques et lesquelles n'en ont pas ?

En JavaScript, cela peut ne pas être intuitif, en TypeScript, les outils (IDE + compilateur) se plaindront. Pourquoi aimons-nous à nouveau TypeScript ? :)

Un getter et un setter JavaScript avec différents types est parfaitement valide et fonctionne très bien et je pense que c'est le principal avantage/objectif de cette fonctionnalité.

Cela fait valoir que JavaScript est faiblement typé, donc TypeScript devrait être faiblement typé. :-S

Cela fait valoir que JavaScript est faiblement typé, donc TypeScript devrait être faiblement typé

C# le permet et cela ne rend pas ce langage faiblement typé C# n'autorise pas l'obtention/la définition de différents types.

C# le permet et cela ne rend pas ce langage faiblement typé C# n'autorise pas l'obtention/la définition de différents types.

:clin d'œil:

Personne ne prétend (pour autant que je sache) que les accesseurs doivent être faiblement typés, ils soutiennent que nous devrions avoir la flexibilité de définir le ou les types.

Il est souvent nécessaire de copier un ancien objet ordinaire dans des instances d'objet.

    get fields(): Field[] {
      return this._fields;
    }

    set fields(value: any[]) {
      this._fields = value.map(Field.fromJson);
    }

C'est beaucoup plus agréable que l'alternative et permet à mon setter d'encapsuler le type exact de setters logiques pour lesquels ils sont faits.

@paulwalker le modèle que vous utilisez ici (un setter prenant un any et un getter renvoyant un type plus spécifique) est valide aujourd'hui.

@danquirk Bon à savoir, merci ! Il semble que j'ai juste besoin de mettre à jour mon compilateur de plugin IDE pour ST.

@danquirk Cela ne semble pas fonctionner selon le terrain de jeu (ou la version 1.6.2):
http://www.typescriptlang.org/Playground#src =%0A%0Aclass%20Foo%20%7B%0A%0A%20%20get%20items()%3A%20string%5B%5D%20%7B%0A %09%20%20retour%20%5B%5D%3B%0A%20%20%7D%0A%20%20%0A%20%20set%20items(value%3A%20any)%20%7B%0A% 09%20%20%0A%20%20%7D%0A%7D

Je viens de tester avec typescript@next (Version 1.8.0-dev.20151102), et j'ai aussi une erreur.

~$ tsc --version
message TS6029: Version 1.8.0-dev.20151102
~$ cat a.ts
class A {
    get something(): number {return 5;}
    set something(x: any) {}
}

~$ tsc -t es5 a.ts
a.ts(2,2): error TS2380: 'get' and 'set' accessor must have the same type.
a.ts(3,2): error TS2380: 'get' and 'set' accessor must have the same type.

Ironiquement, après la mise à jour de mon linter Sublime, il n'y a plus eu d'erreur, qui utilise TypeScript 1.7.x. J'ai supposé qu'il s'agissait d'une amélioration à venir dans 1.7+, donc peut-être que 1.8.0 a régressé.

Même avec la version du code de studio visuel (0.10.5 (décembre 2015)) qui prend en charge le typescript 1.7.5 et avec le typescript 1.7.5 installé globalement sur ma machine, cela reste un problème :

image

Alors, quelle version cela sera pris en charge?
Merci

Je pense que Dan s'est trompé. Le getter et le setter doivent être de type identique.

la honte. aurait été une fonctionnalité intéressante lors de l'écriture d'objets de page à utiliser dans les tests de rapporteur.

J'aurais pu écrire un test de rapporteur:

po.email = "[email protected]";
expect(po.email).toBe("[email protected]");

... en créant un objet page :

class PageObject {
    get email(): webdriver.promise.Promise<string> {
        return element(by.model("ctrl.user.email")).getAttribute("value")
    }
    set email(value: any) {
        element(by.model("ctrl.user.email")).clear().sendKeys(value);
    }
}

Qu'en est-il de ce code qui nécessite que le setter soit de type any ?

Le getter renverra un webdriver.promise.Promise<string> mais le setter je veux attribuer une string .

Peut-être que la forme plus longue suivante du test du rapporteur le rend plus clair :

po.email = "[email protected]";
var currentEmail : webdriver.promise.Promise<string> = po.email;
expect(currentEmail).toBe("[email protected]")

@RyanCavanaugh Avec l'introduction d'annotations null, cela empêche le code qui permet d'appeler un setter avec null pour le définir sur une valeur par défaut.

class Style {
    private _width: number = 5;

    // `: number | null` encumbers callers with unnecessary `!`
    get width(): number {
        return this._width;
    }

    // `: number` prevents callers from passing in null
    set width(newWidth: number | null) {
        if (newWidth === null) {
            this._width = 5;
        }
        else {
            this._width = newWidth;
        }
    }
}

Pourriez-vous envisager au moins de permettre aux types de différer en présence de | null et | undefined ?

Ce serait vraiment une fonctionnalité intéressante.

Alors, est-ce que ce sera une fonctionnalité ?

@artyil , il est fermé et étiqueté _By Design_, ce qui indique qu'il n'est actuellement pas prévu de l'ajouter. Si vous avez un cas d'utilisation convaincant qui, selon vous, l'emporte sur les préoccupations exprimées ci-dessus, vous pouvez vous sentir libre de présenter votre cas et des commentaires supplémentaires peuvent inciter l'équipe principale à reconsidérer sa position.

@kitsonk Je pense que plus qu'assez de cas d'utilisation convaincants ont été fournis dans les commentaires ci-dessus. Alors que la conception actuelle suit un modèle commun à la plupart des autres langages typés avec cette restriction, elle est inutile et trop restrictive dans le contexte de Javascript. Bien que ce soit par conception, la conception est fausse.

Je suis d'accord.

Après y avoir réfléchi un peu plus. Je pense que le problème ici est vraiment la complexité de la mise en œuvre. Personnellement, je trouve l'exemple de @Arnavion convaincant, mais le système de type traite aujourd'hui les getters/setters comme des propriétés normales. pour que cela fonctionne, les deux doivent avoir la même valeur. prendre en charge un type de lecture/écriture serait un changement important, je ne suis pas sûr que l'utilitaire ici vaudrait le coût de mise en œuvre.

Bien que j'adore TypeScript et que j'apprécie tous les efforts que l'équipe y consacre (vraiment, vous êtes cool !), Je dois admettre que je suis déçu de cette décision. Ce serait une bien meilleure solution que les alternatives getFoo()/setFoo() ou get foo()/set foo()/setFooEx() .

Juste une petite liste de problèmes :

  • Nous supposons actuellement que les propriétés ont exactement un type. Nous aurions maintenant besoin de faire la distinction entre le type "lecture" et le type "écriture" de chaque propriété à chaque emplacement
  • Toutes les relations de type deviennent considérablement plus complexes car nous devons raisonner sur deux types par propriété au lieu d'un (est-ce { get foo(): string | number; set foo(): boolean } est attribuable à { foo: boolean | string | number } , ou vice versa ?)
  • Nous supposons actuellement qu'une propriété, après avoir été définie, a toujours le type de la valeur définie sur la ligne suivante (ce qui est apparemment une hypothèse erronée dans les bases de code de certaines personnes, wat ?). Nous aurions probablement juste à "désactiver" toute analyse de contrôle de flux sur des propriétés comme celle-ci

Honnêtement, j'essaie vraiment de m'abstenir d'être normatif ici sur la façon d'écrire du code, mais je m'oppose vraiment à l'idée que ce code

foo.bar = "hello";
console.log(foo.bar);

devrait jamais imprimer quoi que ce soit de plus que "hello" dans un langage qui tente d'avoir une sémantique saine. Les propriétés doivent avoir un comportement indiscernable des champs aux observateurs externes.

@RyanCavanaugh alors que je suis d'accord avec vous sur l'opinion injectée, je peux voir un contre-argument qui _pourrait_ juste être très TypeScripty ... Lancer quelque chose de faiblement typé sur un setter, mais avoir quelque chose de toujours fortement typé renvoyé, par exemple:

foo.bar = [ '1', 2 ];  // any[]
console.log(foo.bar);  // number[]: [ 1, 2 ]

Bien que personnellement, j'ai tendance à penser que si vous allez être aussi magique, il est préférable de créer une méthode afin que le développeur final puisse clairement comprendre ce qui sera plié, plié et mutilé.

Voici notre cas d'utilisation pour cette fonctionnalité. Notre API a introduit une fonctionnalité que nous avons nommée _Autocasting_. Le principal avantage est une expérience développeur simplifiée qui peut éliminer le nombre de classes à importer pour attribuer des propriétés dont le type est bien défini.

Par exemple, une propriété de couleur peut être exprimée sous la forme d'une instance Color ou d'une chaîne CSS telle que rgba(r, g, b, a) ou d'un tableau de 3 ou 4 nombres. La propriété est toujours typée en tant qu'instance de Color , car c'est le type de ce que vous obtenez lors de la lecture de la valeur.

Quelques informations à ce sujet : https://developers.arcgis.com/javascript/latest/guide/autocasting/index.html

Nos utilisateurs ont été très heureux d'obtenir cette fonctionnalité, réduisant le nombre d'importations nécessaires, et comprennent parfaitement que le type change la ligne après l'affectation.

Un autre exemple pour ce problème : https://github.com/gulpjs/vinyl#filebase

file.base = 'd:\\dev';
console.log(file.base); //  'd:\\dev'
file.base = null;
console.log(file.base); //  'd:\\dev\\vinyl' (returns file.cwd)

Ainsi, le setter est string | null | undefined et le getter est juste string . Quel type devrions-nous utiliser dans les définitions de type pour cette bibliothèque ? Si nous utilisons le premier, le compilateur exigerait partout des vérifications nulles inutiles. Si nous utilisons le second, nous ne pourrons pas attribuer null à cette propriété.

J'ai un autre exemple où j'aimerais que le getter renvoie nullable, mais où le setter ne devrait jamais autoriser null comme entrée, stylisé comme:

class Memory {
    public location: string;
    public time: Date;
    public company: Person[];
}

class Person
{
    private _bestMemoryEver: Memory | null;

    public get bestMemoryEver(): Memory | null { // Might not have one yet
        return this._bestMemoryEver;
    }

    public set bestMemoryEver(memory: Memory) { // But when he/she gets one, it can only be replaced, not removed
        this._bestMemoryEver = memory;
    }
}

var someDude = new Person();
// ...
var bestMemory: Memory | null = someDude.bestMemoryEver;
//...
someDude.bestMemoryEver = null; // Oh no you don't!

Je comprends que cela pourrait être trop de travail pour construire une logique spéciale pour permettre aux getters/setters de différer sur null, et ce n'est pas si grave pour moi, mais ce serait bien d'avoir.

@Elephant-Vessel philosophiquement j'adore l'exemple, il représente bien la nature inconstante des êtres humains mais je ne suis pas convaincu qu'il ne représenterait pas cela encore mieux en autorisant null (ou undefined ) à régler. Comment puis-je modéliser l'échec des synapses dans le système ?

@aluanhaddad Pourquoi voudriez-vous une panne de synapse ? Je ne veux pas de ce genre de mauvaises choses dans mon univers ;)

Des mises à jour à ce sujet ? Qu'en est-il d'avoir une valeur par défaut lorsqu'elle est définie sur null ou undefined ?

Je ne veux pas que le consommateur ait à vérifier au préalable. Actuellement, je dois faire du setter une méthode distincte à la place, mais j'aimerais qu'elles soient identiques.

Ci-dessous ce que j'aimerais avoir :

export class TestClass {
  private _prop?: number;

  get prop(): number {
    // return default value if not defined
    this._prop === undefined ? 0 : this._prop;
  }
  set prop(val: number | undefined) {
    this._prop = val;
  }
}

Il me semble que le fait de bénéficier d'une vérification non nulle s'accompagne de pièges, et celui-ci en fait partie. Avec la vérification NULL stricte désactivée, cela est possible, mais vous n'obtenez pas l'aide du compilateur pour empêcher les exceptions de référence NULL. Cependant, si vous voulez de l'aide pour le compilateur, je pense que cela devrait venir avec plus de support, comme avoir des définitions séparées pour les getters et les setters en ce qui concerne au moins la nullabilité si rien d'autre.

Les étiquettes sur le problème indiquent qu'il s'agit d'une limitation de conception et que la mise en œuvre serait considérée comme trop complexe, ce qui signifie essentiellement que si quelqu'un a une raison super impérieuse pour laquelle cela devrait être le cas, cela ne va nulle part.

@mhegazy @kitsonk Je suis peut-être biaisé, mais je pense que c'est un bogue qui est apparu pour une vérification stricte des valeurs nulles sur un modèle commun, en particulier dans d'autres langues comme les accolades où elles n'ont pas encore de vérification nulle. Une solution de contournement nécessiterait que le consommateur utilise l'opérateur bang ou vérifie qu'il n'est jamais nul (ce qui est le point de ne jamais être nul avec l'utilisation des valeurs par défaut).

Cela se décompose une fois que vous ajoutez une vérification nulle stricte, car il s'agit maintenant de types techniquement différents. Je ne demande pas que des types différents forts soient définis, mais il semble que les exigences de conception pour permettre cela permettraient également des types différents forts.

Une conception alternative pourrait être utilisée pour les types faibles, de sorte que des types tels que null et undefined seraient des cas spéciaux pour les définitions d'interface et les fichiers d.ts s'ils ne veulent pas activer des types complètement différents.

En réponse à https://github.com/Microsoft/TypeScript/issues/2521#issuecomment -199650959
Voici une conception de proposition qui devrait être moins complexe à mettre en œuvre :

export interface Test {
  undefset prop1: number; // property [get] type number and [set] type number | undefined
  nullset prop2: number; // property [get] type number and [set] type number | null
  nilset prop3: number; // property [get] type number and [set] type number | null | undefined
  undefget prop4: number; // property [get] type number | undefined and [set] type number
  nullget prop5: number; // property [get] type number | null and [set] type number
  nilget prop6: number; // property [get] type number | null | undefined and [set] type number
}

Il semble que certaines personnes qui regardent ce fil soient beaucoup plus familières avec TypeScript, alors peut-être que quelqu'un qui fait toujours attention peut répondre à une question connexe. Sur ce problème de Cesium, j'ai mentionné la limitation de type get/set dont nous discutons ici, et les gens de Cesium ont dit que le modèle qu'ils suivent vient de C# - c'est le constructor implicite .

TypeScript peut-il prendre en charge les constructeurs implicites ? Autrement dit, puis-je dire que myThing.foo renvoie toujours un Bar , et peut être affecté directement à un Bar , mais peut également être affecté à un number qui sera être tranquillement enveloppé dans / utilisé pour initialiser un Bar , pour la commodité du développeur ? S'il est possible de le faire en annotant Bar , ou peut-être en disant spécifiquement que " number est attribuable à Bar<number> ", cela répondrait au cas d'utilisation discuté dans le problème de Cesium, ainsi que de nombreux problèmes soulevés dans ce fil.

Si ce n'est pas le cas, dois-je suggérer la prise en charge implicite des constructeurs dans un problème distinct ?

Plus je pense / lis à ce sujet, plus je suis certain que le "modèle de constructeur implicite" va avoir besoin de la fonctionnalité décrite dans ce numéro. La seule façon possible dans vanilla JS est d'utiliser des accesseurs get/set d'objet, car c'est la seule fois où l'affectation nominale (opérateur = ) appelle réellement une fonction définie par l'utilisateur. (N'est-ce pas?) Donc, je pense que nous aurons vraiment besoin

class MyThing{
  set foo(b: Bar<boolean> | boolean);
  get foo(): Bar<boolean>;
}

ce qui ressemble à @RyanCavanaugh pense que ce n'est pas une "sémantique sensée".

Le simple fait est qu'il existe une bibliothèque JS assez populaire qui utilise ce modèle, et il semble qu'il soit difficile, voire impossible, de le décrire compte tenu des contraintes TS existantes. J'espère que je me trompe.

JavaScript autorise déjà le modèle que vous décrivez. Le _challenge_ est que les côtés lecture et écriture des types dans TypeScript sont, par conception, supposés être les mêmes. Il y a eu une modification du langage pour interdire l'affectation ( readonly ) mais il y a quelques problèmes qui ont demandé le concept _write only_, qui ont été discutés comme trop complexes pour peu de valeur réelle.

IMO, depuis que JavaScript a autorisé les accesseurs, les gens ont potentiellement créé des API déroutantes avec eux. Personnellement, je trouve déroutant que quelque chose en mission change _magiquement_ en quelque chose d'autre. Tout ce qui est implicite, en particulier la conversion de type, est le fléau de JavaScript IMO. C'est justement la flexibilité qui pose problème. Avec ce type de conversions, où il y a _magic_, j'aime personnellement voir des méthodes appelées, où il est un peu plus explicite pour le consommateur qu'une sorte de ✨ se produira et fera un getter en lecture seule pour récupérer des valeurs.

Cela ne signifie pas que l'utilisation dans le monde réel n'existe pas, c'est potentiellement sensé et rationnel. Je suppose que cela revient à la complexité de diviser l'ensemble du système de type en deux, où les types doivent être suivis sur leurs conditions de lecture et d'écriture. Cela semble être un scénario très non trivial.

Je suis d'accord sur le :sparkles: ici, mais je n'argumente pas pour ou contre l' utilisation du modèle, j'essaie juste de venir derrière le code qui l'utilise déjà et d'en décrire la forme. Cela fonctionne déjà comme cela fonctionne, et TS ne me donne pas les outils pour le décrire.

Pour le meilleur ou pour le pire, JS nous a donné la possibilité de transformer des affectations en appels de fonction et les gens l'utilisent. Sans la possibilité d'attribuer différents types aux paires set/get , pourquoi même avoir set/get dans les typages ambiants ? Salsa n'a pas besoin de savoir qu'une propriété est implémentée avec un getter et un setter si elle va toujours traiter myThing.foo comme une variable membre d'un seul type, quel que soit le côté de l'affectation. (Évidemment, la compilation TypeScript réelle est une tout autre chose.)

@thw0rted

Il semble que certaines personnes qui regardent ce fil soient beaucoup plus familières avec TypeScript, alors peut-être que quelqu'un qui fait toujours attention peut répondre à une question connexe. Sur ce problème de Cesium, j'ai mentionné la limitation de type get/set dont nous discutons ici, et les gens de Cesium ont dit que le modèle qu'ils suivent vient de C# -- c'est le constructeur implicite.

Les opérateurs de conversion implicites définis par l'utilisateur de C# effectuent la génération de code statique en fonction des types de valeurs. Les types TypeScript sont effacés et n'influencent pas le comportement d'exécution ( async / await cas extrêmes pour Promise polyfillers non supportés).

@kitsonk Je dois être en désaccord en général en ce qui concerne les propriétés. Ayant passé pas mal de temps avec Java, C++ et C#, j'adore les propriétés (comme on le voit en C#) car elles fournissent une abstraction syntaxique critique (c'est un peu vrai en JavaScript). Ils permettent aux interfaces de séparer les capacités de lecture/écriture de manière significative sans utiliser de syntaxe changeante. Je déteste voir des verbes gaspillés sur des opérations triviales comme getX() alors que l'obtention X peut être implicite.
Les API déroutantes de l'OMI, dont beaucoup font comme vous le dites, abusent des accesseurs, découlent davantage de trop de _setters_ font des choses magiques.

Si j'ai une vue en lecture seule, mais en direct sur certaines données, disons un registry , je pense que les propriétés en lecture seule sont très faciles à comprendre.

interface Entry {key: string; value: any;}

export function createRegistry() {
  let entries: Entry[] = [];
  return {
    register(key: string, value: any) {
      entries = [...entries, {key, value}];
    },
    get entries() {
      return [...entries];
    }
  }
}

const registry = createRegistry();

registry.register('hello', '您好');
console.log(registry.entries); //[{key: 'hello', value: '您好'}]
registry.register('goodbye', '再见');
console.log(registry.entries); //[{key: 'hello', value: '您好'}, {key: 'goodbye', value: '再见'}]

Désolé pour la tangente, mais j'adore les accesseurs pour cela et je pense qu'ils sont faciles à comprendre mais je suis prêt à être convaincu du contraire et la lisibilité est ma première préoccupation.

Lorsque TypeScript limite JavaScript, cela devient plus une nuisance qu'un avantage. TypeScript n'est-il pas destiné à aider les développeurs à communiquer entre eux ?

De plus, les setters sont appelés Mutators pour une raison. Si je n'avais besoin d'aucun type de conversion, je n'utiliserais pas de setter, je définirais la variable par moi-même.

portage du projet javascript vers dactylographie. j'ai rencontré ce problème..

Ce serait bien à utiliser sur les décorateurs angulaires @Input . Étant donné que la valeur est transmise à partir d'un modèle, il serait beaucoup plus propre, à mon avis, de traiter les différents types d'objets entrants.

Mise à jour : cela semble fonctionner pour moi

import { Component, Input } from '@angular/core';
import { flatMap, isString, isArray, isFalsy } from 'lodash';

@Component({
  selector: 'app-error-notification',
  templateUrl: './error-notification.component.html',
})

export class ErrorNotificationComponent {
  private _errors: Array<string> = [];
  constructor() { }
  /**
   * 'errors' is expected to be an input of either a string or an array of strings
   */
  @Input() set errors(errors: Array<string> | any){
      // Caller just passed in a string instead of an array of strings
      if (isString(errors)) {
        this._errors = [errors];
      }
      // Caller passed in array, assuming it is a string array
      if (isArray(errors)) {
        this._errors = errors;
      }
      // Caller passed in something falsy, which means we should clear error list
      if (isFalsy(errors)) {
        this._errors = [];
      }
      // At this point just set it to whatever might have been passed in and let
      // the user debug when it is broken.
      this._errors = errors;
  }

  get errors() {
    return this._errors;
  }
}

Alors étions-nous ? J'aimerais vraiment avoir la possibilité de retourner un type différent avec le getter que depuis le setter. Par exemple:

class Field {
  private _value: string;

  get value(): string {
    return this._value;
  }

  set value(value: any) {
    this._value = String(value);
  }
}

C'est ce que font 99% des implémentations natives (si vous passez un nombre à un (input as HTMLInputElement).value il renverra toujours une chaîne. En fait get et set doivent être considérés comme des méthodes et devraient en autoriser certaines :

set value(value: string);
set value(value: number);
set value(value: any) {
  this._value = String(value);
}
  // AND/OR
set value(value: string | number) {
  this._value = String(value);
}

@raysuelzer , quand vous dites que votre code "fonctionne", ErrorNotificationComponent.errors ne renvoie-t-il pas un type de Array<string> | any ? Cela signifie que vous avez besoin de protections de type à chaque fois que vous l'utilisez, alors que vous savez qu'il ne peut réellement renvoyer Array<string> .

@lifaon74 , autant que je sache, il n'y a pas de mouvement sur cette question. Je pense qu'un cas convaincant a été présenté - plusieurs scénarios présentés, des tonnes de code JS hérité qui ne peuvent pas être correctement décrits dans Typescript à cause de cela - mais le problème est clos. Peut-être que si vous pensez que les faits sur le terrain ont changé, ouvrez-en un nouveau ? Je ne connais pas la politique de l'équipe sur le rechapage des vieux arguments, mais je te soutiendrais.

Je ne connais pas la politique de l'équipe sur le rechapage des vieux arguments, mais je te soutiendrais.

Ils reviendront sur des sujets précédemment fermés. Fournir un 👍 en haut du problème donne de la crédibilité en ce sens qu'il est significatif. Je crois qu'il est généralement tombé dans la catégorie "trop ​​complexe" car cela signifierait que le système de type devrait être bifurqué à chaque lecture et écriture et je soupçonne que le sentiment est l'effort et le coût de l'intégrer pour satisfaire ce qui est un cas d'utilisation valide mais quelque peu inhabituel n'en vaut pas la peine.

Mon sentiment personnel est que ce serait un _nice to have_, en particulier pour pouvoir modéliser le code JavaScript existant qui utilise efficacement ce modèle.

Personnellement, je considérerais le problème comme résolu si nous trouvions une solution de contournement pas totalement onéreuse pour le problème de JS hérité. Je suis toujours un peu un débutant TS, alors peut-être que ma solution actuelle (lancement forcé ou gardes de type inutiles) n'est pas une utilisation optimale des capacités existantes ?

Accepter. Le setter sera tellement limité lorsque le type doit être le même... Veuillez améliorer.

Vous recherchez des recommandations pour y parvenir :

get price() {
    return (this._price as number);
  }

  set price(price) {
    this._price = typeof price === 'string' ? parseFloat(parseFloat(price).toFixed(8)) : parseFloat(price.toFixed(8));
  }

Je veux que la valeur stockée soit un nombre, mais je veux convertir une chaîne en flottant dans le setter afin de ne pas avoir à parseFloat chaque fois que je définis une propriété.

Je pense que le verdict est qu'il s'agit d'un modèle Javascript valide (ou du moins raisonnablement bien accepté), qui ne correspond pas bien aux composants internes de Typescript. Peut-être que si la modification des composants internes est trop importante, nous pourrions obtenir une sorte d'annotation qui modifie la façon dont le TS compile en interne ? Quelque chose comme

class Widget {
  get price(): number | /** <strong i="7">@impossible</strong> */ string | undefined { return this._price; }
  set price(val: number|string|undefined){ ... }
}

let w = new Widget();
w.price = 10;
// Annotation processes as "let p:number|undefined = (w.price as number|undefined)"
let p: number|undefined = w.price;

En d'autres termes, nous pourrions peut-être baliser le TS de sorte que le préprocesseur (?) convertisse toutes les lectures de w.price en un cast explicite, avant que le TS ne soit transpilé en JS. Bien sûr, je ne connais pas les rouages ​​de la façon dont TS gère les annotations, donc cela pourrait être une poubelle totale, mais l'idée générale est qu'il serait peut-être plus facile de transformer en quelque sorte le TS en un autre TS, que de changer la façon dont le transpileur TS génère JS.

Je ne dis pas que cette fonctionnalité n'est pas nécessaire, mais voici un moyen de contourner pour set . IMO get devrait toujours renvoyer le même type de variable, donc c'était assez bon pour moi.

class Widget {
    get price(): number { return this._price; }
    set price(val){ return this.setPrice(val); } // call another function

    // do processing here
    private setPrice(price: number | string): number {
        let num = Number(price);
        return isNaN(num) ? 0 : num;
    }
}

@iamjoyce widget.price = '123' émet l'erreur de compilation dans votre code

@iamjoyce => faux car le compilateur suppose val: number dans set price(val)

Ouais, je suis aussi ici parce que l'utilisation des composants par Angular semble vouloir que nous ayons différents types de getter/setter.

Angular injectera toujours une chaîne si vous définissez une propriété de composants (via un attribut) à partir du balisage (sauf si vous utilisez une expression de liaison) quel que soit le type de la propriété d'entrée. Donc, ce serait certainement bien de pouvoir modéliser ceci comme ceci:

private _someProperty: SomeEnum;
set someProperty(value: SomeEnum | string) {
   this._someProperty = this.coerceSomeEnum(value);
} 
get someProperty(): SomeEnum {
  return this._someProperty;
}

Aujourd'hui, cela fonctionne si nous laissons de côté le | string mais avoir cela décrirait plus précisément comment Angular finit par utiliser le composant. En ce sens que si vous y accédez à partir du code, le type de la propriété est important, mais si vous le définissez comme un attribut, il forcera brutalement une chaîne.

Je ne pense pas que nous voulions généralement cette fonctionnalité car nous aimerions concevoir des API de cette façon. Je suis d'accord, que les propriétés sont meilleures si elles ne font pas d'effets secondaires de coercition sous les couvertures. Pour contourner ce problème, ce serait formidable si Angular avait été conçu de telle sorte que les ensembles d'attributs par rapport aux ensembles de propriétés de liaison entreraient dans des points d'entrée différents.

Mais si nous regardons cela de manière pragmatique, il est utile que TypeScript nous permette de modéliser les interactions avec les bibliothèques externes qui utilisent nos types d'une manière qui défie l'axiome d'égalité de type lecture/écriture.

Dans ce cas, il serait très problématique d'utiliser le type union sur le getter, car cela nécessiterait toutes sortes de gardes/affirmations de type ennuyeuses. Mais laisser le type d'union hors du setter semble mal car n'importe quel outil pourrait décider plus tard de commencer à essayer de vérifier que les propriétés définies à partir d'attributs doivent être assignables à partir de string.

Donc, dans ce cas, le système de type n'est pas assez expressif pour saisir comment une bibliothèque externe est autorisée à utiliser le type. Cela n'a pas d'importance _immédiatement_, car la bibliothèque externe ne consomme pas réellement ces types au niveau du tapuscrit, avec les informations de type. Mais cela peut éventuellement avoir de l'importance, car l'outillage peut très bien consommer les informations de type.

Une personne ci-dessus a mentionné une solution alternative qui semble un peu désagréable, mais qui pourrait probablement fonctionner, dans laquelle nous pourrions utiliser le type d'union pour le setter/getter, mais avoir un moyen d'indiquer que certains types de l'union sont impossibles pour le getter. Sont donc, en fait, pré-supprimés de la considération dans le flux de contrôle du site du site d'appel getter comme si quelqu'un avait utilisé un type de garde pour vérifier qu'il n'est pas présent. Cela semble ennuyeux par rapport à l'autorisation d'une union de sur-ensemble sur le setter par rapport à une union de sous-ensemble sur le getter.

Mais c'est peut-être une façon de régler les choses en interne. Tant que le setter est juste une union de sur-ensemble du type getter. Traitez les types du getter et du setter comme équivalents en interne, mais marquez les parties du type d'union du getter comme impossibles. De sorte que l'analyse du flux de contrôle les élimine de toute considération. Cela contournerait-il les contraintes de conception?

Pour développer ce qui précède. Peut-être serait-il utile, du point de vue de l'expressivité d'un type, de pouvoir indiquer des portions d'un type composé comme étant impossibles. Cela n'aurait pas d'impact sur l'égalité avec un autre type avec la même structure mais sans les modificateurs impossibles. Un modificateur impossible n'aurait d'impact que sur l'analyse du flux de contrôle.

Comme suggestion alternative, s'il y avait une syntaxe pour appliquer une protection de type définie par l'utilisateur à une valeur de retour, cela suffirait également. Je me rends compte que c'est, dans des circonstances normales, un peu ridicule, mais avoir ce genre d'expressivité sur la valeur de retour ainsi que sur les arguments aiderait à résoudre ces cas extrêmes.

La protection de type définie par l'utilisateur sur la valeur de retour a également l'avantage d'être quelque chose qui pourrait être exprimable dans les déclarations/interfaces de type lorsqu'elle est écrasée dans une propriété ?

En ajoutant simplement un autre cas d'utilisation ici, mobx-state-tree permet de définir une propriété de différentes manières (à partir d'instances et de types d'instantanés), mais il ne la renverra que d'une seule manière standard (instances) à partir de l' accesseur get , ce serait donc extrêmement utile s'il était pris en charge.

Je contourne ça comme ça :

interface SomeNestedString {
  foo: string;
}

...

private _foo: SomeNestedString | string;

get foo(): SomeNestedString | string {
  return this._foo;
}

set foo(value: SomeNestedString | string) {
  this._foo = (value as SomeNestedString).foo;
}

Je ne pense pas que cela fonctionne autour de la question à portée de main. Vous auriez toujours besoin de gardes de type lors de l'utilisation du getter, même si, en réalité, le getter ne renvoie qu'un sous-ensemble du type union.

J'utilise any pour contourner TSLint. Et le compilateur ne se plaint pas non plus.

export class FooBar {
  private bar: string;

  get foo (): string | any {
    return this.bar;
  }

  set foo (value: Date | string | any) {
    // Type guarding enables IntelliSense in VS Code
    if (value instanceof Date) {
      this.bar = value.toISOString();
    } else if (typeof value === 'string') {
      this.bar = value;
    } else {
      this.bar = String(value); // Or throw an error
    }
  }
}

@jpidelatorre de cette façon, vous perdez la sécurité du type.

const fooBar = new FooBar()
const a: number = fooBar.foo // works while it should fail
fooBar.foo = 123 // fails only at runtime, not compile time. It doesnt fail in this particular case with strings and numbers, but it will with something more complex

@keenondrums C'est exactement pourquoi nous voulons que les accesseurs aient différents types. Ce que j'ai trouvé est une solution de contournement, pas une solution.

@jpidelatorre ma solution de contournement actuelle consiste à utiliser une autre fonction en tant que setter

export class FooBar {
  private bar: string;

  get foo (): string {
    return this.bar;
  }

  setFoo (value: Date | string ) {}
}

@keenondrums Pas aussi beau que les accessoires, mais la meilleure option à ce jour.

Pas vraiment un modèle pour ce qui se passe avec les propriétés @input sur un composant angulaire, sauf si vous utilisez une fonction distincte pour un getter.

Ce qui est juste trop moche.

J'aimerais aussi cette fonctionnalité, mais j'en ai besoin pour bien fonctionner dans les fichiers .d.ts, où il n'y a pas de solution de contournement. J'essaie de documenter certaines classes exposées via Mocha (le pont Objective-C/Javascript), et les propriétés d'instance des éléments enveloppés sont configurées comme suit :

class Foo {
    get bar:()=>number;
    set bar:number;
}

const foo = new Foo();
foo.bar = 3;
foo.bar(); // 3

J'ai également vu de nombreux cas où une API vous permet de définir une propriété avec un objet correspondant à une interface, mais le getter renvoie toujours une instance de classe réelle :

interface IFoo {
    bar: string;
}

class Foo implements IFoo {
    bar: string;
    toString():string;
}

class Example {
    get foo:Foo;
    set foo:Foo|IFoo;
}

J'avais en grande partie oublié ce problème, mais une pensée m'est venue en le relisant. Je pense que nous nous sommes mis d'accord sur l'idée que cela vaut la peine d'être fait dans l'abstrait, mais trop compliqué techniquement pour être faisable. C'est un calcul de compromis - ce n'est pas techniquement impossible , cela ne vaut tout simplement pas le temps et les efforts (et la complexité du code supplémentaire) à mettre en œuvre. Droit?

Le fait que cela rend impossible la description précise de l'API DOM principale change-t-il les calculs ? @RyanCavanaugh dit

Je m'oppose vraiment à l'idée que ce code foo.bar = "hello"; console.log(foo.bar); devrait jamais imprimer autre chose que "bonjour" dans un langage qui tente d'avoir une sémantique saine.

Nous pourrions nous demander s'il devrait être utilisé de cette manière, mais le DOM a toujours pris en charge des constructions telles que el.hidden=1; console.log(el.hidden) // <-- true, not 1 . Ce n'est pas seulement un modèle que quelques personnes utilisent, ce n'est pas seulement qu'il se trouve dans une bibliothèque populaire, il peut donc être judicieux de le prendre en charge. C'est un principe fondamental de la façon dont JS a toujours fonctionné - un peu de DWIM intégré à l'âme du langage - et le rendre impossible au niveau du langage enfreint le principe fondamental de TS, à savoir qu'il doit s'agir d'un "surensemble" de JS . C'est une vilaine bulle qui sort du diagramme de Venn, et nous ne devrions pas l'oublier.

C'est pourquoi j'aime toujours l'idée de garder le setter/getter ayant le même type:

number | boolean

Mais introduisez une sorte de typegaurd où vous pouvez indiquer que, bien que le getter ait techniquement le même type d'union, en réalité, il ne renverra jamais qu'un sous-ensemble des types de l'union. Ne le traite-t-il pas comme une sorte de garde-fou rétrécissant sur le getter (un truc de flux d'inférence?) Ne le rend-il pas plus simple qu'une modification du modèle de type? (Il dit ne rien savoir des internes...)

Cela pourrait, alternativement, être simplement implicite si le type utilisé sur le getter était un sous-ensemble strict du type setters?

Cela pourrait, alternativement, être simplement implicite si le type utilisé sur le getter était un sous-ensemble strict du type setters?

👍 !

Je pense que nous pouvons tous convenir que vous ne devriez pas pouvoir obtenir un type qui n'était pas paramétrable ; Je voudrais juste un moyen de réduire automatiquement le type sur get pour qu'il soit le type que je sais qu'il sera toujours.

Je ne comprends pas pourquoi certains objectent que cela violerait la sécurité du type et tout. Je ne vois pas que cela viole la sécurité de type si nous autorisons au moins le setter à avoir un type différent, car de toute façon nous avons un contrôle en place que vous ne permettrez pas à un autre type de définir une propriété.
Ex:
Dire que j'ai une propriété comme Arraymais à partir de DB, cela sera renvoyé sous forme de chaîne avec une séparation par des virgules comme par exemple "10,20,40". Mais je ne peux pas mapper cela à la propriété mode maintenant, donc ce serait très utile si vous pouviez autoriser comme

privé _employeeIdList : nombre[]

obtenir EmployeeIDList() : nombre[] {
renvoie this._employeeIdList ;
}
set EmployeeIDList(_idList : tout ) {
si (typeof _idList== 'chaîne') {
this._employeeIdList = _idList.split(',').map(d => Number(d));
}
sinon si (typeof _idList== 'objet') {
this._employeeIdList = _idList as number[] ;
}
}

J'aurais facilement résolu ce problème et il est parfaitement sûr, même si vous autorisez un type différent dans SET, mais cela nous empêche toujours d'attribuer un mauvais type à la propriété. Alors gagnant gagnant. J'espère que les membres de l'équipe poseront leur ego et essaieront de penser au problème que cela crée et de le résoudre.

J'aimerais dire que je pense toujours que c'est vraiment important pour modéliser le comportement des bibliothèques JS existantes.

Bien qu'il soit bien beau de tourner le nez et de définir un setter qui contraint un type à un sous-ensemble du type entrant et renvoie toujours ce sous-ensemble dans le getter comme une odeur de code, en réalité, c'est un modèle raisonnablement courant dans JS land .

Je pense que TypeScript est agréable en ce sens qu'il nous permet d'être très expressifs lors de la description des cagaries des API JS existantes, même si elles n'ont pas d'API idéalement structurées. Je pense que c'est un scénario où si TypeScript était suffisamment expressif pour nous permettre d'indiquer que le getter renverrait toujours un type qui est le sous-ensemble strict du type du getter, cela ajouterait énormément de valeur à la modélisation des API existantes, même le DOM !

Trusted Types est une nouvelle proposition d'API de navigateur pour lutter contre DOM XSS. Il est déjà implémenté dans Chromium (derrière un drapeau). La majeure partie de l'API modifie les setters de diverses propriétés DOM pour accepter les types de confiance. Par exemple, .innerHTML accepte TrustedHTML | string alors qu'il renvoie toujours string . Pour décrire l'API dans TypeScript, nous aurions besoin que ce problème soit résolu.

La différence avec les commentaires précédents est qu'il s'agit d'une API de navigateur (et non d'une bibliothèque utilisateur) qui ne peut pas être modifiée facilement. De plus, l'impact du changement du type de Element.innerHTML en any (ce qui est la seule solution actuellement possible) est plus important que la description imprécise d'une bibliothèque utilisateur.

Y a-t-il une chance que cette pull-request soit rouverte ? Ou y a-t-il d'autres solutions que j'ai manquées?

Cc : @mprobst , @koto.

Dans un langage qui prend en charge l'union de types comme TypeScript, cette fonctionnalité est naturelle et constitue un argument de vente.

@RyanCavanaugh même lorsque le getter et le setter ont le même type, il n'est pas garanti que o.x === y après o.x = y car le setter peut effectuer une désinfection avant d'enregistrer la valeur.

element.scrollTop = -100;
element.scrollTop; // returns 0

Je partage la préoccupation de @vrana. Le comportement actuel rend impossible la modélisation de certaines des API existantes dans Typescript.

Cela est particulièrement vrai pour les API Web, pour lesquelles les types setter et getter sont différents. En pratique, les setters d'API Web effectuent toutes sortes de coercition de type, certains d'entre eux étant spécifiés directement pour une fonctionnalité de navigateur donnée, mais la plupart d'entre eux implicitement via IDL . De nombreux setters modifient également la valeur, voir par exemple la spécification de l'interface de localisation . Ce n'est pas une seule erreur de développeur - c'est une spécification de l'API avec laquelle les développeurs Web codent.

La réduction des types de getter permet à Typescript de représenter ces API, ce qui est désormais impossible.

Vous pouvez représenter ces API car il est correct de dire que le type de la propriété est l'union des types possibles que vous pouvez fournir au setter ou obtenir du getter.

Ce n'est tout simplement pas _efficace_ de décrire une API de cette façon. Vous demandez au consommateur d'utiliser une protection de type dans chaque cas où il utilise le getter pour réduire les types possibles.

Ce n'est pas grave si vous n'avez pas rendu axiomatique le fait que vous retournerez toujours un type restreint à partir d'un getter, car de nombreuses API Web se verrouillent même dans leur spécification.

Mais même en mettant cela de côté pour un moment, et en parlant d'API utilisateur, un cas d'utilisation fort pour accepter les types d'union sur un setter est celui "scripty". Nous voulons accepter une gamme de types discrets que nous pouvons contraindre de manière acceptable au type que nous voulons réellement.

Pourquoi permettre cela ? Facilité d'utilisation.

Cela peut ne pas avoir d'importance pour les API que vous développez en interne pour l'utilisation de votre propre équipe, mais peut avoir beaucoup d'importance pour les API conçues pour une consommation publique et généralisée.

C'est charmant que TypeScript puisse nous permettre de décrire avec précision une API qui a assoupli les types acceptables pour certaines de ses propriétés, mais cet avantage est gâché par la friction à l'autre extrémité où une vérification/protection de type excessive est nécessaire pour déterminer le type de retour getter , que nous préférerions _spécifier_.

Je dirais que c'est un scénario différent du cas idéalisé que pose @RyanCavanaugh . Ce cas implique que vous getter et setter devriez toujours avoir le même type d'union parce que votre champ de sauvegarde a également le même type d'union, et vous ferez toujours un aller-retour avec cette valeur, et changer son type est tout simplement absurde.

Je pense que ce cas est centré sur une utilisation plus idéalisée des types d'union, dans laquelle vous avez affaire à des types construits et traitez réellement ce type d'union comme une unité sémantique, pour laquelle vous auriez probablement dû créer un alias.

type urlRep = string | Url;

La plupart des choses feront l'aller-retour et ne traiteront que des accessoires courants, et dans certains cas, vous casserez la boîte noire avec certains types de gardes.

Je dirais que c'est un scénario entièrement différent de ce que nous décrivons ici. Ce que nous décrivons est la réalité que les API à usage général/public, en particulier pour une utilisation dans des langages de script comme JavaScript, assouplissent souvent délibérément les types acceptables pour un setter, car il existe une gamme de types qu'ils accepteront de contraindre au type idéal, donc ils l'offrent comme une amélioration de la qualité de vie pour accepter tout cela.

Ce genre de chose peut sembler absurde si vous êtes à la fois le producteur et le consommateur d'une API, car cela ne fait que demander plus de travail, mais peut avoir beaucoup de sens si vous concevez une API pour une consommation de masse.

Et je ne pense pas que quiconque voudrait que le setter/getter ait des types _disjoints_. Ce qui est discuté ici est que le producteur d'API affirme au consommateur d'API que le getter renverra une valeur avec un type qui est un sous-ensemble strict du type d'union du setter.

Et je ne pense pas que quiconque voudrait que le setter/getter ait des types disjoints.

Juste pour fournir un contre-point de vue à cela. Je le voudrais certainement car avoir des types disjoints pour setter/getter est tout à fait légal en javascript. Et je pense que l'objectif principal devrait être de rendre le système de type suffisamment expressif pour taper correctement tout ce qui est légal en javascript (au moins au niveau .d.ts). Je comprends que ce n'est pas une bonne pratique, ni les objets globaux par exemple, ni la modification du prototype des fonctions intégrées comme la fonction, etc., mais nous sommes toujours capables de les taper très bien dans les fichiers .d.ts et je n'ai entendu personne le regretter nous pouvons le faire (même si cela provoque l'apparition de fichiers de définition de type mal conçus sur des typés définitifs).

Je viens de tomber sur une situation où j'ai besoin que ce soit une fonctionnalité afin d'avoir les types corrects que je ne contrôle pas. Le setter doit être plus libéral et contraindre à un type plus conservateur qui peut être renvoyé de manière cohérente par le getter.

J'aimerais vraiment que cette fonctionnalité existe. Je porte du code JavaScript sur TypeScript, et c'est vraiment dommage que je doive refactoriser les setters si je veux la sécurité de type.

Par exemple, j'ai quelque chose comme ça :

class Vector3 { /* ... */ }

type XYZ = Vector3 | [number, number, number] | {x: number, y: number, z: number}
type PropertyAnimator = (x: number, y: number, z: number, timestamp: number) => XYZ
type XYZSettables =  XYZ | PropertyAnimator

export class Transformable {
        // ...

        set position(newValue: XYZSettables) {
            this._setPropertyXYZ('position', newValue)
        }
        get position(): Vector3 {
            return this._props.position
        }

        // ...
}

Et comme vous pouvez l'imaginer, l'utilisation est très flexible :

const transform = new Transformable

// use an array
transform.position = [20, 30, 40]

// use an object
transform.position = {y: 30, z: 40} // skip `x` this time

// animate manually, a property directly
requestAnimationFrame((time) => {
  transform.position.x = 100 * Math.sin(time * 0.001)
})

// animate manually, with an array, which could be shared across instances
const pos = [10, 20, 30]
requestAnimationFrame((time) => {
  pos[2] = 100 * Math.sin(time * 0.001)
  transform.position = pos
})

// Animate with a property function
transform.position = (x, y, z, time) => [x, y, 100 * Math.sin(time * 0.001)]

// or a simple increment:
transform.position = (x, y, z) => [x, y, ++z]

// etc

// etc

// etc

Celui-ci a 4 ans. Comment ça c'est TOUJOURS pas réparé ? Cette fonctionnalité, comme mentionné dans les commentaires ci-dessus, est significative ! Pensez au moins à rouvrir le sujet ?

Et je suis complètement d'accord avec @kitsonk :

@kitsonk Je pense que plus qu'assez de cas d'utilisation convaincants ont été fournis dans les commentaires ci-dessus. Alors que la conception actuelle suit un modèle commun à la plupart des autres langages typés avec cette restriction, elle est inutile et trop restrictive dans le contexte de Javascript. Bien que ce soit par conception, la conception est fausse.

TypeScript 3.6 a introduit une syntaxe pour taper les accesseurs dans les fichiers de déclaration , tout en conservant la restriction selon laquelle le type du getter et du setter doit être identique.

Notre boîte à outils d'interface utilisateur s'appuie fortement sur les setters à diffusion automatique, où le setter accepte plus de types que le type singulier renvoyé par le getter. Ce serait donc bien si TS offrait un moyen de rendre ce modèle sûr pour le type.

Il semble que @RyanCavanaugh ait donné la réfutation la plus concrète de cette fonctionnalité il y a 3 ans . Je me demande si les cas d'utilisation DOM récemment signalés ainsi que la disponibilité d'une nouvelle syntaxe de déclaration pourraient permettre une nouvelle décision ?

Oui, dès que j'ai vu cette nouvelle fonctionnalité, j'ai immédiatement pensé à cette demande de fonctionnalité également. Je le veux également pour les besoins de nos cadres d'interface utilisateur. C'est un vrai cas d'utilisation, les gars.

La TSConf 2019 aura lieu le 11 octobre , pensant que ce serait une excellente rétrospective en session de questions-réponses, si quelqu'un en avait l'occasion 🤔

Je l'ai peut-être déjà dit sur ce long fil, mais je pense que cela mérite d'être répété.

IMO, le système de type expressif dans TypeScript sert à deux fins distinctes. La première vous permet d'écrire un nouveau code plus sûr. Et si c'était le seul but, vous pourriez peut-être faire valoir que vous pourriez éviter ce cas d'utilisation car le code pourrait être plus sûr si vous deviez interdire ce scénario (mais je pense que nous pourrions toujours avoir un argument à ce sujet).

Cependant, le deuxième objectif est de capturer le comportement des bibliothèques telles qu'elles sont et c'est une pratique raisonnablement courante dans le DOM et dans les bibliothèques JS de contraindre automatiquement le setter, mais de renvoyer un type attendu dans le getter. Nous permettre de capturer cela dans le système de types nous permet de décrire plus précisément les frameworks existants tels qu'ils sont .

À tout le moins, je pense que Design Limitation pourrait être supprimé ici, je ne pense pas que cela soit plus pertinent.

C'est apparemment la raison du fait que par #33749 .style.display = ...something nullable... ne se tape plus; qui est présenté comme un problème d'exactitude de la nullité. C'est un peu malhonnête non ? C'est ennuyeux de devoir trouver de nouveaux changements de rupture lorsqu'ils sont mal étiquetés comme des corrections de bogues (je suis allé chercher des problèmes réels que cela aurait pu causer). Personnellement, je trouve beaucoup moins surprenant que null ait un comportement spécial "utiliser par défaut", que la chaîne vide ; et jusqu'à typescipt 3.7, je préférais utiliser null pour capturer cela. Dans tous les cas, ce serait bien si les annotations de type intentionnellement incorrectes faites pour contourner cette limitation étaient clairement étiquetées comme telles, pour gagner du temps lors du triage des problèmes de mise à niveau.

Je suis également intéressé à trouver une voie à suivre pour cela. Et si cela n'était autorisé que dans des contextes ambiants ? @RyanCavanaugh cela aiderait-il à résoudre vos problèmes de complexité ?

Mon cas d'utilisation pour cela est que j'ai une API où un proxy renvoie une promesse, mais une opération set ne définit pas de promesse. Je ne peux pas décrire cela dans TypeScript pour le moment.

let post = await loadPost()
let user = await loadUser()
post.author = user // Proxy handles links these two objects via remote IDs
await save(post)

// Somewhere else in code
let post = await loadPost()
let author = await post.author

Qu'il s'agisse de fonctions natives de correction de type ou de proxys en général, TypeScript semble être d'avis que ces types de fonctionnalités étaient une erreur.

Ils devraient vraiment retirer les numéros 6 et 7 de cette liste (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals)

Mon problème

Je voudrais pousser un élément vers un tableau dans la méthode set et obtenir le tableau entier dans la méthode get. Pour l'instant, je vais devoir utiliser set myArray(...value: string[]) qui fonctionne bien. J'ai beaucoup travaillé sur ce problème... veuillez envisager de le supprimer. Une information ou un avertissement fonctionnerait très bien pour cela.

Exemple (ce que je voudrais faire)

class MyClass {
   _myArray: string[] = [];

   set myArray(value: string) {
      this._myArray.push(value);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Exemple (ce que je dois faire)

class MyClass {
   _myArray: string[] = [];

   set myArray(...value: string[]) {
      this._myArray.push(value[0]);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Une solution que j'ai trouvée lorsque j'en avais besoin était de définir la propriété sur union type . C'est parce que mon temps arrive sous la forme d'une chaîne de l'API, mais je dois définir comme objet Moment pour mon interface utilisateur.

Mon exemple

class TimeStorage {
    _startDate: string | Moment = ""
    _endDate: string | Moment = ""

    set startDate(date: Moment | string) {
        this._startDate = moment(date).utc()
    }
    get startDate(): Moment | string {
        return moment.utc(this._startDate)
    }

    set endDate(date: Moment) { 
        this._endDate = moment.utc(date)
    }
    get endDate() { 
        return moment.utc(this._endDate)
    }
}

Je suis un peu en retard pour la fête, mais j'ai récemment rencontré ce problème moi-même. C'est un dilemme intéressant, alors j'ai un peu trifouillé dessus.

Supposons que c'est quelque chose que nous voulons faire, une classe simple qui a une propriété qui accepte des chaînes et des nombres (quel qu'en soit le but) mais la propriété est vraiment stockée sous forme de chaîne :

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    get bar(): string {
        return this._bar;
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    set bar(value: string | number) {
        this._bar = value.toString();
    }

    public baz: string;
    private _bar: string;
}

Comme ce n'est pas possible et que nous voulons toujours un typage fort, nous avons besoin de code supplémentaire. Supposons maintenant, pour les besoins de notre exemple, que quelqu'un ait créé une bibliothèque Property qui ressemble à ceci :

/**
 * Abstract base class for properties.
 */
abstract class Property<GetType, SetType> {
    abstract get(): GetType;
    abstract set(value: SetType): void;
}

/**
 * Proxify an object so that it's get and set accessors are proxied to
 * the corresponding Property `get` and `set` calls.
 */
function proxify<T extends object>(obj: T) {
    return new Proxy<any>(obj, {
        get(target, key) {
            const prop = target[key];
            return (prop instanceof Property) ? prop.get() : prop;
        },
        set(target, key, value) {
            const prop = target[key];
            if (prop instanceof Property) {
                prop.set(value);
            } else {
                target[key] = value;
            }
            return true;
        }
    });
}

En utilisant cette bibliothèque, nous pourrions implémenter notre propriété typée pour notre classe comme ceci :

class Bar extends Property<string, string | number> {
    constructor(bar: string | number) {
        super();
        this.set(bar);
    }

    get(): string {
        return this._bar;
    }

    set(value: string | number) {
        this._bar = value.toString();
    }

    private _bar: string;
}

class Foo {
    constructor() {
        this.bar = new Bar('');
        this.baz = '';
    }

    public bar: Bar;
    public baz: string;
}

Et puis, l'utilisation de cette classe aurait un getter et un setter sûrs pour bar :

const foo = new Foo();

// use property's typed setter
foo.bar.set(42);
foo.baz = 'foobar';

// use property's typed getter
// output: 42 foobar
console.log(foo.bar.get(), foo.baz);

Et si vous avez besoin d'utiliser les setters ou les getters de propriété de manière plus dynamique, vous pouvez utiliser la fonction proxify pour l'envelopper :

const foo = new Foo();

// use proxified property setters
Object.assign(proxify(foo), { bar: 100, baz: 'hello world' });

// use property's typed getter
// output: 100 hello world
console.log(foo.bar.get(), foo.baz);

// use proxified property getters
// output: {"bar":"100","baz":"hello world"}
console.log(JSON.stringify(proxify(foo)));

Avis de non-responsabilité : si quelqu'un veut prendre ce code et le mettre dans une bibliothèque ou en faire ce qu'il veut, n'hésitez pas à le faire. Je suis trop paresseux.

Juste quelques notes supplémentaires et ensuite je promets que j'arrêterai de spammer cette longue liste de personnes !

Si nous ajoutons ceci à la bibliothèque imaginaire :

/**
 * Create a property with custom `get` and `set` accessors.
 */
function property<GetType, SetType>(property: {
    get: () => GetType,
    set: (value: SetType) => void
}) {
    const obj = { ...property };
    Object.setPrototypeOf(obj, Property.prototype);
    return obj;
}

Nous obtenons une implémentation un peu plus proche de la classe d'origine, et surtout a accès à this :

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    public bar = property({
        get: (): string => {
            return this._bar;
        },
        set: (value: string | number) => {
            this._bar = value.toString();
        }
    });

    public baz: string;
    private _bar: string;
}

Ce n'est pas la plus belle chose au monde mais ça marche. Peut-être que quelqu'un peut affiner cela et faire quelque chose de vraiment sympa.

Dans le cas peu probable où un individu lisant ce fil ne serait pas convaincu que cette fonctionnalité est importante, je vous donne ce hack incroyablement laid qu'Angular vient d'introduire :

L'idéal serait de changer le type de valeur ici, de booléen à booléen|'', pour correspondre à l'ensemble de valeurs qui sont réellement acceptées par le setter. TypeScript exige que le getter et le setter aient le même type, donc si le getter doit renvoyer un booléen, le setter est bloqué avec le type le plus étroit... Angular prend en charge la vérification d'un type plus large et plus permissif pour @Input() que ce n'est le cas déclaré pour le champ de saisie lui-même. Activez cela en ajoutant une propriété statique avec le préfixe ngAcceptInputType_ à la classe du composant :

````
class SubmitButton {
privé _disabled : booléen ;

se désactiver() : booléen {
retourne this._disabled;
}

set désactivé (valeur : booléen) {
this._disabled = (valeur === '') || évaluer;
}

ngAcceptInputType_disabled statique : booléen|'' ;
}
````

S'il vous plaît, s'il vous plaît , corrigez cela.

Oui, nous avons littéralement cette laideur partout pour prendre en charge les modèles que Angular veut utiliser. La restriction actuelle est trop opiniâtre.

Si TS veut être utilisé pour modéliser l'ensemble des bibliothèques sur le Web, il doit s'efforcer d'être moins opiniâtre, sinon il ne parviendra pas à modéliser toutes les conceptions.

À ce stade, je pense qu'il incombe aux contributeurs de communiquer pourquoi ce problème n'est pas rouvert ? Y a-t-il un membre contributeur qui est même fortement contre cela à ce stade ?

Nous construisons une bibliothèque DOM pour NodeJs qui correspond à la spécification W3C, mais ce problème de Typescript rend cela impossible. Toute mise à jour?

Je suis choqué que certains membres de l'équipe principale de TypeScript s'opposent à une fonctionnalité OBLIGATOIRE pour recréer l'une des bibliothèques JavaScript les plus utilisées sur la planète : le DOM.

Il n'y a pas d'autre moyen de a) implémenter de nombreuses parties de la spécification DOM du W3C tout en b) en utilisant le système de type de TypeScript (à moins que vous n'ayez recours à l'utilisation any partout, ce qui va à l'encontre de l'objectif de TypeScript).

La page d'accueil de typescriptlang.org est actuellement inexacte lorsqu'elle indique :

TypeScript est un sur- ensemble typé de JavaScript qui se compile en JavaScript brut.

L'impossibilité de recréer la spécification DOM de JavaScript montre que TypeScript est toujours un sous- ensemble de Javascript PAS un sur- ensemble .

En ce qui concerne l'implémentation de cette fonctionnalité, je suis d'accord avec @gmurray81 et bien d'autres qui ont fait valoir que le type de getter doit être un sous-ensemble du type d'union d'un setter. Cela garantit qu'il n'y a pas de saisie disjointe. Cette approche permet à la fonction d'un setter de nettoyer l'entrée sans détruire le type du getter (c'est-à-dire, être obligé de recourir à l'utilisation any ).

Voici ce que je ne peux pas faire. Quelque chose de simple que j'ai fait de similaire en JS mais je ne peux pas en TS.

Problème de 4 ans qui pourrait VRAIMENT être mis en œuvre.

export const enum Conns {
  none = 0, d = 1, u = 2, ud = 3,
  r = 4, rd = 5, ru = 6, rud = 7,
  l = 8, ld = 9, lu = 10, lud = 11,
  lr = 12, lrd = 13, lru = 14, lrud = 15,
  total = 16
}

class  Tile {
  public connections = Conns.none;

  get connLeft() { return this.connections & Conns.l; };

  set connLeft(val: boolean) { this.connections = val ? (this.connections | Conns.l) : (this.connections & ~Conns.l); }
}

Courir dans ce problème maintenant. Ce qui suit semble être une solution sensée, pour citer @calebjclark :

En ce qui concerne l'implémentation de cette fonctionnalité, je suis d'accord avec @gmurray81 et bien d'autres qui ont fait valoir que le type de getter doit être un sous-ensemble du type d'union d'un setter. Cela garantit qu'il n'y a pas de saisie disjointe. Cette approche permet à la fonction d'un setter de nettoyer l'entrée sans détruire le type du getter (c'est-à-dire, être obligé de recourir à any).

Car pour le getter étant un sous-ensemble du type du setter, je pense que c'est inutilement limité. Vraiment, le type d'un setter _pourrait_ théoriquement être littéralement n'importe quoi, tant que l'implémentation renvoie toujours un sous-type du type du getter. Exiger que le setter soit un supertype du type du getter est une sorte de limitation étrange qui pourrait répondre aux quelques cas d'utilisation présentés dans ce ticket, mais qui entraînerait sûrement des plaintes plus tard.

Cela étant dit, les classes sont implicitement aussi des interfaces, les getters et les setters étant essentiellement des détails d'implémentation qui n'apparaîtraient pas dans une interface. Dans l'exemple d'OP, vous vous retrouveriez avec type MyClass = { myDate(): moment.Moment; } . Vous devriez d'une manière ou d'une autre exposer ces nouveaux types de getter et de setter dans le cadre de l'interface, bien que je ne vois pas personnellement pourquoi cela serait souhaitable.

Ce problème demande essentiellement une version moins limitée de la surcharge d'opérateur de = et === . (Les getters et les setters surchargent déjà les opérateurs.) Et en tant que personne ayant traité des surcharges d'opérateurs dans d'autres langages, je dirai que je pense qu'elles devraient être évitées dans la plupart des cas. Bien sûr, quelqu'un pourrait suggérer un exemple mathématique comme les points cartésiens et polaires, mais dans la grande majorité des cas où vous utiliseriez TS (y compris les exemples de ce fil), je dirais que la surcharge de l'opérateur est probablement une odeur de code. Cela peut sembler simplifier l'API, mais cela a tendance à faire le contraire. Comme mentionné précédemment, le fait que les éléments suivants ne soient pas nécessairement vrais est très déroutant et peu intuitif.

foo.x = y;
if (foo.x === y) { // this could be false?

(Je pense que les seules fois où j'ai vu des setters utilisés qui semblaient raisonnables sont pour la journalisation et pour définir un indicateur "sale" sur des objets afin que quelque chose d'autre puisse dire si un champ a été modifié. Ni l'un ni l'autre ne change vraiment le contrat du objet.)

@MikeyBurkman

[...] mais dans la grande majorité des cas où vous utiliseriez TS (y compris les exemples de ce fil), je dirais que le besoin d'une surcharge d'opérateur est probablement une odeur de code [...]

 foo.x = y; 
 if (foo.x === y) { // this could be false?

Ainsi, le simple fait d'avoir des correspondances de types n'empêche pas un comportement surprenant, et en effet el.style.display = ''; //so is this now true: el.style.display === ''? montre que ce n'est pas non plus théorique. Même avec de vieux champs simples, l'hypothèse ne tient pas pour NaN, soit dit en passant.

Mais plus important encore, votre argument ignore le fait que TS ne peut pas vraiment avoir d'opinion sur ces choses parce que TS doit interopérer avec les API JS existantes, y compris les éléments majeurs et peu susceptibles de changer des choses comme le DOM. Et en tant que tel, peu importe que l'API soit idéale ou non ; ce qui compte, c'est que TS ne puisse pas interopérer proprement avec une API donnée . Maintenant, vous êtes obligé soit de réimplémenter la logique de secours que l'API a utilisée en interne pour contraindre une valeur de type out-of-getter transmise au setter et donc de taper la propriété en tant que type getters, soit d'ignorer la vérification de type pour ajouter des assertions de type inutiles sur chaque site getters et tapez donc la propriété comme type de setters. Ou, pire : faites tout ce que TS choisit d'annoter pour le DOM, que ce soit idéal ou non.

Tous ces choix sont mauvais. La seule façon d'éviter cela est d'accepter la nécessité de représenter les apis JS le plus fidèlement possible, et là : cela semble possible (certes sans aucune connaissance des internes de TS qui peuvent rendre cela décidément non trivial).

cela semble possible (certes sans aucune connaissance des composants internes de TS qui peuvent rendre cela décidément non trivial)

Je soupçonne qu'une partie de la nouvelle syntaxe de déclaration de type qu'ils ont activée plus récemment pourrait rendre cela exprimable alors que c'était moins faisable auparavant, ils devraient donc vraiment envisager de rouvrir ce problème ... @RyanCavanaugh

Dans un monde idéal, TS interopérerait avec toutes les API JS. Mais ce n'est pas le cas. Il existe un tas d'idiomes JS qui ne peuvent pas être saisis correctement dans TS. Cependant, je préfère qu'ils se concentrent sur la réparation de choses qui conduisent réellement à de meilleurs idiomes de programmation, pas pires. Bien que soutenir cela de manière sensée serait bien, il y a une douzaine d'autres choses sur lesquelles je préférerais personnellement qu'ils passent leur temps limité en premier. C'est en bas de cette liste.

@MikeyBurkman la question de priorité est valable. Ma principale préoccupation est la décision de @RyanCavanaugh de fermer ce problème et de l'annuler. Le rejet de tous les problèmes signalés sur ce fil entre directement en conflit avec la mission déclarée de Typescript, qui est d'être un "surensemble" de Javascript (au lieu d'un sous-ensemble).

Oui, je peux certainement convenir que ce problème ne devrait probablement pas être clos, car c'est quelque chose qui _probablement_ devrait être éventuellement résolu. (Bien que je doute vraiment que ce soit le cas.)

Bien que soutenir cela de manière sensée serait bien, il y a une douzaine d'autres choses sur lesquelles je préférerais personnellement qu'ils passent leur temps limité en premier

Je pense que vous sous-estimez un aspect important de TypeScript en ce sens qu'il est censé rendre plus sûr l'utilisation des API DOM existantes et des API JS existantes. Il existe pour garder plus que votre propre code, dans sa bulle, en toute sécurité.

Voici mon point de vue, je construis des bibliothèques de composants, et même si je ne provoquerais pas nécessairement délibérément cette inadéquation entre les setters/getters, compte tenu de mes druthers. Parfois, ma main est forcée en fonction de la façon dont mes bibliothèques doivent s'interfacer avec d'autres systèmes en place. Par exemple, Angular définit toutes les propriétés d'entrée sur un composant provenant du balisage sous forme de chaînes, plutôt que de faire une coercition de type basée sur leur connaissance du type cible (au moins, la dernière fois que j'ai vérifié). Alors, refusez-vous d'accepter les chaînes ? Faites-vous de tout une chaîne, même si cela rendrait vos types horribles à utiliser ? Ou faites-vous ce que TypeScript voudrait que vous fassiez et utilisez un type tel que : string | Colorez mais rendez l'utilisation des getters horrible. Ce sont toutes des options assez terribles qui réduisent la sécurité, alors qu'une expressivité supplémentaire du système de type aurait aidé.

Le problème est que ce n'est pas seulement Angular qui cause ces problèmes. Angular s'est retrouvé dans cette situation car il reflète de nombreux scénarios dans le DOM où l'auto-coercition se produit sur un ensemble de propriétés, mais le getter est toujours un type singulier anticipé.

Regardez un peu vers le haut : Angular est bien pire que vous ne le pensez .

Pour moi, c'est généralement le problème lorsque vous souhaitez créer un wrapper autour d'un stockage à usage général, qui peut stocker des paires clé-valeur et que vous souhaitez limiter les utilisateurs à certaines clés.

class Store {
  private dict: Map<string, any>;

  get name(): string | null {
    return this.dict.get('name') as string | null;
  }

  set name(value: string) {
    this.dict.set('name', value);
  }
}

Vous voulez avoir une telle contrainte : l'utilisateur peut obtenir null , si la valeur n'a pas été définie précédemment, mais ne peut pas la définir sur null . Actuellement, vous ne pouvez pas le faire, car le type d'entrée du setter doit inclure null .

@fan-tom, excellent cas d'utilisation pour expliquer pourquoi ce problème doit être rouvert ! Laissez-les venir.

Cette question a un nombre extrêmement élevé de votes positifs !

C'est le projet de l'équipe TS, ils peuvent donc faire ce qu'ils veulent comme bon leur semble. Mais s'ils visent à rendre cela aussi utile que possible pour une communauté externe d'utilisateurs, j'espère que l'équipe TS pourra prendre en compte le nombre élevé de votes de la communauté.

La page d'accueil de typescriptlang.org est actuellement inexacte lorsqu'elle indique :

TypeScript est un sur- ensemble typé de JavaScript qui se compile en JavaScript brut.

TypeScript est un _superset_ typé d'un _sous-ensemble_ de JavaScript qui se compile en JavaScript brut. :smiley:

TypeScript est un sur-ensemble typé d'un sous-ensemble de JavaScript qui se compile en JavaScript brut.

Plus diplomatiquement, je pense que vous pourriez dire que c'est un sur-ensemble opiniâtre de JavaScript, lorsque l'équipe fait ce genre d'omission opiniâtre de la modélisation du système de type.

Ils disent, "nous ne modéliserons pas cela, parce que c'est difficile pour nous de le faire, et nous pensons que vous ne devriez pas le faire, de toute façon". Mais JavaScript regorge de choses que vous ne devriez pas faire, et généralement Typescript ne vous empêchera pas de faire ces choses si vous en avez la volonté et le savoir-faire, car il semble que la stratégie générale consiste à ne pas avoir d'opinion sur ce que JavaScript vous pouvez et ne pouvez pas simplement exécuter en tant que Typescript.

C'est pourquoi il est si étrange de refuser de modéliser ce scénario courant (utilisé dans le DOM !), Citant la conviction que les API ne devraient pas effectuer de coercition basée sur le setter comme justification pour ne pas le faire.

Actuellement, cela ne semble pas être possible, et je dois recourir à quelque chose comme ceci :

class MyClass {

    private _myDate: moment.Moment;

    get myDate(): moment.Moment {
        return this._myDate;
    }

    set myDate(value: moment.Moment) {
        assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
    }

    setMyDate(value: Date | moment.Moment) {
        this._myDate = moment(value);
    }
}

Le mieux que vous puissiez faire est d'ajouter un constructeur et d'y appeler la fonction setter personnalisée, si c'est la seule fois que vous définissez cette valeur.

constructor(value: Date | moment.Moment) {
    this.setMyDate(value);
}

Le problème lors de l'attribution directe des valeurs demeure.

Une mise à jour à ce sujet, après plus de 5,5 ans ?

@xhliu comme l'indiquent les étiquettes, il s'agit d'une limitation de conception trop complexe à traiter, et donc le problème est clos. Je ne m'attendrais pas à une mise à jour. La durée d'un problème clos est en quelque sorte sans importance.

Veuillez rouvrir. Cela doit également être ajouté au niveau de l'interface. Un exemple classique est la mise en œuvre d'un proxy où vous avez une flexibilité totale sur ce que vous pouvez lire et écrire.

@xhliu ... trop complexe à traiter ...

https://ts-ast-viewer.com/#code/MYewdgzgLgBFCm0DyAjAVjAvDA3gKBkJgDMQQAuGAIhQEMAnKvAXzzwWXQDpSQg

const testObj = {
    foo: "bar"
}

testObj.foo

Le symbole foo a ceci à dire :

  foo
    flags: 4
    escapedName:"foo"
    declarations: [
      PropertyAssignment (foo)
    ]
    valueDeclaration: PropertyAssignment (foo)

Il y a beaucoup de place ici pour des informations supplémentaires, ce qui est une excellente conception de la part de TS. Celui qui a conçu cela l'a probablement fait spécifiquement pour rendre possibles des fonctionnalités supplémentaires (même les plus importantes) à l'avenir.

Spéculatif à partir de maintenant :

S'il était possible de déclarer (pour un exemple de pseudo-code) un accessDelclaration: AccessSpecification (foo) , alors le PropertyAccessExpression qui connaît le symbole foo et ses déclarations, pourrait vérifier conditionnellement s'il y a un "accessDelclaration" et utilisez la frappe à partir de cela à la place.

En supposant que la syntaxe existe pour ajouter cette propriété accessDelclaration au symbole "foo", PropertyAccessExpression devrait pouvoir extraire la "AccessSpecification" du symbole qu'elle obtient de ts.createIdentifier("foo") et produire différents types.

ts.createPropertyAccess(
  ts.createIdentifier("testObj"),
  ts.createIdentifier("foo")
)

De manière spéculative, il semble que la partie la plus difficile de ce défi serait probablement la quantité de vélo perdu autour de la syntaxe (ou peut-être une philosophie d'entreprise ?), Mais du point de vue de la mise en œuvre, les outils devraient tous être là. Une seule condition serait ajoutée à la fonction ts.createPropertyAccess() , et une classe Declaration pour représenter cette condition et ses effets doit être ajoutée au symbole de la propriété de l'objet.

De nombreux bons exemples ont été écrits pour expliquer pourquoi cela est désespérément nécessaire (en particulier pour DOM et Angular).

J'ajouterai simplement que j'ai été touché par cela aujourd'hui lors de la migration de l'ancien code JS vers TS où l'attribution string à window.location n'a pas fonctionné et j'ai dû faire as any solution de contournement 😟

La propriété en lecture seule Window.location renvoie un objet Location avec des informations sur l'emplacement actuel du document.

Bien que Window.location soit un objet Location en lecture seule, vous pouvez également lui attribuer un DOMString. Cela signifie que vous pouvez travailler avec location comme s'il s'agissait d'une chaîne dans la plupart des cas : location = ' http://www.example.com ' est un synonyme de location.href = ' http://www.example.com ' .
la source

la migration de l'ancien code JS vers TS où l'attribution string à window.location n'a pas fonctionné et j'ai dû faire une solution de contournement as any

C'est un excellent exemple.

TS en a besoin. C'est une partie très normale de JavaScript.

Je vois que le sujet en cours a été fermé. Mais si je comprends bien, cette fonctionnalité n'a pas été réalisée?

@AGluk , ce problème doit être rouvert.

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

Questions connexes

manekinekko picture manekinekko  ·  3Commentaires

CyrusNajmabadi picture CyrusNajmabadi  ·  3Commentaires

wmaurer picture wmaurer  ·  3Commentaires

siddjain picture siddjain  ·  3Commentaires

blendsdk picture blendsdk  ·  3Commentaires