Comment attribuer dynamiquement des propriétés à un objet dans TypeScript?

359

Si je voulais assigner par programme une propriété à un objet en Javascript, je le ferais comme ceci:

var obj = {};
obj.prop = "value";

Mais en TypeScript, cela génère une erreur:

La propriété 'prop' n'existe pas sur la valeur de type '{}'

Comment suis-je censé attribuer une nouvelle propriété à un objet dans TypeScript?

Peter Olson
la source
1
Pour info. J'ai ouvert une discussion à ce sujet si vous souhaitez la suivre.
joshuapoehls

Réponses:

456

Il est possible de désigner objcomme any, mais cela va à l'encontre du but de l'utilisation du tapuscrit. obj = {}implique objest un Object. Le marquer comme anyn'a aucun sens. Pour atteindre la cohérence souhaitée, une interface peut être définie comme suit.

interface LooseObject {
    [key: string]: any
}

var obj: LooseObject = {};

OU pour le rendre compact:

var obj: {[k: string]: any} = {};

LooseObjectpeut accepter des champs avec n'importe quelle chaîne comme clé et anytaper comme valeur.

obj.prop = "value";
obj.prop2 = 88;

La vraie élégance de cette solution est que vous pouvez inclure des champs de type sécurisé dans l'interface.

interface MyType {
    typesafeProp1?: number,
    requiredProp1: string,
    [key: string]: any
}

var obj: MyType ;
obj = { requiredProp1: "foo"}; // valid
obj = {} // error. 'requiredProp1' is missing
obj.typesafeProp1 = "bar" // error. typesafeProp1 should be a number

obj.prop = "value";
obj.prop2 = 88;

Bien que cela réponde à la question d'origine, la réponse ici de @GreeneCreations pourrait donner une autre perspective sur la façon d'aborder le problème.

Akash Kurian Jose
la source
13
Je pense que c'est la meilleure solution maintenant. Je pense qu'au moment où la question a été posée, les propriétés d'index comme celle-ci n'étaient pas encore implémentées dans TypeScript.
Peter Olson
que diriez-vous de l'objet natif comme Erreur
Wayou
Les objets natifs @Wayou comme Error et Array héritent de Object. Cela LooseObjectpeut également prendre une erreur.
Akash Kurian Jose
Pouvez-vous vérifier si l'objet a une certaine propriété (dynamique)?
phhbr
1
pour ces objets en vrac, comment les écrivez-vous sous forme de getter-setter?
JokingBatman
80

Ou tout d'un coup:

  var obj:any = {}
  obj.prop = 5;
Crwth
la source
42
Quel est l'intérêt de TypeScript si je dois lancer autant de choses anypour l'utiliser?
Devient
16
@AjaxLeung Si vous devez le faire, vous l'utilisez mal.
Alex
2
Voir la modification supplémentaire ci-dessus qui préserve le type précédent de l'objet.
Andre Vianna
6
@AjaxLeung Vous devriez très rarement caster sur any. TypeScript est utilisé pour détecter les erreurs (potentielles) au moment de la compilation. Si vous lancez anypour couper les erreurs, vous perdez le pouvoir de taper et vous pouvez aussi revenir à JS pur. anydevrait idéalement être utilisé uniquement si vous importez du code pour lequel vous ne pouvez pas écrire de définitions TS ou lors de la migration de votre code de JS vers TS
Precastic
63

J'ai tendance à mettre anyde l'autre côté, var foo:IFoo = <any>{};donc quelque chose comme ça est toujours sûr:

interface IFoo{
    bar:string;
    baz:string;
    boo:string;     
}

// How I tend to intialize 
var foo:IFoo = <any>{};

foo.bar = "asdf";
foo.baz = "boo";
foo.boo = "boo";

// the following is an error, 
// so you haven't lost type safety
foo.bar = 123; 

Vous pouvez également marquer ces propriétés comme facultatives:

interface IFoo{
    bar?:string;
    baz?:string;
    boo?:string;    
}

// Now your simple initialization works
var foo:IFoo = {};

Essayez-le en ligne

basarat
la source
5
+1 pour être la seule solution qui conserve la sécurité des caractères. Assurez-vous simplement d'instancier toutes les propriétés non facultatives directement après, pour éviter que les bogues ne vous mordent plus tard.
Aidiakapi
Est-ce que cela fonctionne réellement? Après avoir compilé, j'ai encore <any> {} dans mon javascript.
bvs
4
I still have <any>{alors vous n'avez pas compilé. TypeScript supprimerait cela dans son émission
basarat
4
Ces jours-ci, var foo: IFoo = {} as anyest préféré. L'ancienne syntaxe de transtypage était en collision avec TSX (Typescript-ified JSX).
Don
51

Cette solution est utile lorsque votre objet a un type spécifique. Comme lors de l'obtention de l'objet à une autre source.

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.
jmvtrinidad
la source
7
C'est la bonne solution pour les missions ponctuelles.
soundly_typed
Cette réponse a fonctionné pour moi car pour la connexion à Facebook, j'ai dû ajouter des propriétés à l' objet fenêtre . Ma première utilisation du mot clé as dans TypeScript.
AlanObject
33

Bien que le compilateur se plaigne, il devrait toujours le produire selon vos besoins. Cependant, cela fonctionnera.

var s = {};
s['prop'] = true;
Angelo R.
la source
2
oui bien, ce n'est pas vraiment du type script façon, vous perdez intellisense.
pregmatch
26

Une autre option consiste à accéder à la propriété en tant que collection:

var obj = {};
obj['prop'] = "value";

Aviw
la source
2
C'est la manière la plus concise. Object.assign(obj, {prop: "value"})de ES6 / ES2015 fonctionne aussi.
Charles
9

Je suis surpris qu'aucune des réponses ne fasse référence à Object.assign car c'est la technique que j'utilise chaque fois que je pense à la "composition" en JavaScript.

Et cela fonctionne comme prévu dans TypeScript:

interface IExisting {
    userName: string
}

interface INewStuff {
    email: string
}

const existingObject: IExisting = {
    userName: "jsmith"
}

const objectWithAllProps: IExisting & INewStuff = Object.assign({}, existingObject, {
    email: "[email protected]"
})

console.log(objectWithAllProps.email); // [email protected]

Les avantages

  • tapez la sécurité tout au long parce que vous n'avez pas du tout besoin d'utiliser le anytype
  • utilise le type d'agrégat de TypeScript (comme indiqué par &lors de la déclaration du type de objectWithAllProps), qui indique clairement que nous composons un nouveau type à la volée (c'est-à-dire dynamiquement)

Choses à savoir

  1. Object.assign a ses propres aspects uniques (bien connus des développeurs JS les plus expérimentés) qui doivent être pris en compte lors de l'écriture de TypeScript.
    • Il peut être utilisé de manière modifiable ou de manière immuable (je démontre la manière immuable ci-dessus, ce qui signifie qu'il existingObjectreste intact et n'a donc pas de emailpropriété. Pour la plupart des programmeurs de style fonctionnel, c'est une bonne chose car le résultat est le seul nouveau changement).
    • Object.assign fonctionne mieux lorsque vous avez des objets plus plats. Si vous combinez deux objets imbriqués qui contiennent des propriétés nullables, vous pouvez finir par remplacer les valeurs véridiques par undefined. Si vous faites attention à l'ordre des arguments Object.assign, cela devrait aller.
GreeneCreations
la source
Pour moi, dans les cas où vous devez utiliser cela pour afficher des données, cela semble fonctionner correctement, mais lorsque vous devez ajouter des entrées de types modifiés à un tableau, cela ne semble pas fonctionner trop bien. J'ai eu recours à la composition dynamique de l'objet et à son affectation sur une seule ligne, puis à l'attribution des propriétés dynamiques sur des lignes successives. Cela m'a permis de faire la plupart du chemin, alors merci.
Shafiq Jetha
8

Vous pouvez créer un nouvel objet basé sur l'ancien objet à l'aide de l'opérateur d'étalement

interface MyObject {
    prop1: string;
}

const myObj: MyObject = {
    prop1: 'foo',
}

const newObj = {
    ...myObj,
    prop2: 'bar',
}

console.log(newObj.prop2); // 'bar'

TypeScript déduira tous les champs de l'objet d'origine et VSCode effectuera la saisie semi-automatique, etc.

Alex
la source
bien, mais au cas où vous auriez besoin d'utiliser prop2 dans par exemple prop3, sera difficile à implémenter
ya_dimon
6

Le plus simple suivra

const obj = <any>{};
obj.prop1 = "value";
obj.prop2 = "another value"
Jayant Varshney
la source
5

Il est possible d'ajouter un membre à un objet existant en

  1. élargir le type (lire: étendre / spécialiser l'interface)
  2. caster l'objet d'origine en type étendu
  3. ajouter le membre à l'objet
interface IEnhancedPromise<T> extends Promise<T> {
    sayHello(): void;
}

const p = Promise.resolve("Peter");

const enhancedPromise = p as IEnhancedPromise<string>;

enhancedPromise.sayHello = () => enhancedPromise.then(value => console.info("Hello " + value));

// eventually prints "Hello Peter"
enhancedPromise.sayHello();
Daniel Dietrich
la source
1
la meilleure solution pour moi. J'ai appris quelque chose de nouveau aujourd'hui, merci
Maurice
je reçois une erreur TS2352 avec ce code
ya_dimon
@ya_dimon essayez-le sur le Playground TypeScript: typescriptlang.org/play Cela fonctionne très bien!
Daniel Dietrich
@DanielDietrich fonctionne maintenant .. thx, aucune idée de ce qui s'est passé.
ya_dimon
vous êtes les bienvenus! changement se produit;)
Daniel Dietrich
5

Puisque vous ne pouvez pas faire cela:

obj.prop = 'value';

Si votre compilateur TS et votre linter ne vous stricte pas, vous pouvez écrire ceci:

obj['prop'] = 'value';

Si votre compilateur TS ou linter est strict, une autre réponse serait de transtyper:

var obj = {};
obj = obj as unknown as { prop: string };
obj.prop = "value";
LEMUEL ADANE
la source
3
C'est si 'noImplicitAny: false' sur le tsconfig.json
LEMUEL ADANE
Sinon, vous pouvez répondre à GreeneCreations.
LEMUEL ADANE
4

Stockez toute nouvelle propriété sur n'importe quel type d'objet en la transtypant en «n'importe quoi»:

var extend = <any>myObject;
extend.NewProperty = anotherObject;

Plus tard, vous pouvez le récupérer en redéfinissant votre objet étendu sur 'any':

var extendedObject = <any>myObject;
var anotherObject = <AnotherObjectType>extendedObject.NewProperty;
Erlend Robaye
la source
C'est totalement la bonne solution. Disons que vous avez un objet let o: ObjectType; .... plus tard, vous pouvez convertir o en n'importe quel (<any> o) .newProperty = 'foo'; et il peut être récupéré comme (<any> o) .newProperty. Aucune erreur de compilation et fonctionne comme un charme.
Matt Ashley
cela freine l'intellisense ... y a-t-il un moyen d'y parvenir mais de garder l'intellisense?
pregmatch
4

Pour garantir que le type est un Object(c'est -à- dire des paires clé-valeur ), utilisez:

const obj: {[x: string]: any} = {}
obj.prop = 'cool beans'
bersling
la source
4
  • Cas 1:

    var car = {type: "BMW", model: "i8", color: "white"}; car['owner'] = "ibrahim"; // You can add a property:

  • Cas 2:

    var car:any = {type: "BMW", model: "i8", color: "white"}; car.owner = "ibrahim"; // You can set a property: use any type

ikarayel
la source
4

vous pouvez utiliser ceci:

this.model = Object.assign(this.model, { newProp: 0 });
HamidReza
la source
3

Vous pouvez ajouter cette déclaration pour désactiver les avertissements.

declare var obj: any;

joshuapoehls
la source
3

La meilleure pratique est d'utiliser une saisie sécurisée, je vous recommande:

interface customObject extends MyObject {
   newProp: string;
   newProp2: number;
}
Isidro Martínez
la source
3

Pour conserver votre type précédent, transformez temporairement votre objet en n'importe quel

  var obj = {}
  (<any>obj).prop = 5;

La nouvelle propriété dynamique ne sera disponible que lorsque vous utiliserez le cast:

  var a = obj.prop; ==> Will generate a compiler error
  var b = (<any>obj).prop; ==> Will assign 5 to b with no error;
Andre Vianna
la source
3

Voici une version spéciale de Object.assign, qui ajuste automatiquement le type de variable à chaque changement de propriété. Pas besoin de variables supplémentaires, d'assertions de types, de types explicites ou de copies d'objets:

function assign<T, U>(target: T, source: U): asserts target is T & U {
    Object.assign(target, source)
}

const obj = {};
assign(obj, { prop1: "foo" })
//  const obj now has type { prop1: string; }
obj.prop1 // string
assign(obj, { prop2: 42 })
//  const obj now has type { prop1: string; prop2: number; }
obj.prop2 // number

//  const obj: { prop1: "foo", prop2: 42 }

Remarque: l' exemple utilise des fonctions d'assertion TS 3.7 . Le type de retour de assignest void, contrairement à Object.assign.

ford04
la source
1

Si vous utilisez Typescript, vous souhaitez probablement utiliser la sécurité de type; auquel cas Object nu et 'any' sont contre-indiqués.

Mieux vaut ne pas utiliser Object ou {}, mais certains types nommés; ou vous utilisez peut-être une API avec des types spécifiques, que vous devez étendre avec vos propres champs. J'ai trouvé que cela fonctionnait:

class Given { ... }  // API specified fields; or maybe it's just Object {}

interface PropAble extends Given {
    props?: string;  // you can cast any Given to this and set .props
    // '?' indicates that the field is optional
}
let g:Given = getTheGivenObject();
(g as PropAble).props = "value for my new field";

// to avoid constantly casting: 
let k:PropAble = getTheGivenObject();
k.props = "value for props";
Jack Punt
la source
1

affecter dynamiquement des propriétés à un objet dans TypeScript.

pour ce faire, il vous suffit d'utiliser des interfaces dactylographiées comme ceci:

interface IValue {
    prop1: string;
    prop2: string;
}

interface IType {
    [code: string]: IValue;
}

vous pouvez l'utiliser comme ça

var obj: IType = {};
obj['code1'] = { 
    prop1: 'prop 1 value', 
    prop2: 'prop 2 value' 
};
Nerdroid
la source
1
J'essaie avec votre code, mais je ne reçois aucune erreur: pastebin.com/NBvJifzN
pablorsk
1
essayez d'initialiser le attributeschamp à l'intérieur SomeClass et cela devrait le corriger public attributes: IType = {}; pastebin.com/3xnu0TnN
Nerdroid
1

Essaye ça:

export interface QueryParams {
    page?: number,
    limit?: number,
    name?: string,
    sort?: string,
    direction?: string
}

Ensuite, utilisez-le

const query = {
    name: 'abc'
}
query.page = 1
Tung Nguyen
la source
0

La seule solution entièrement sécurisée est celle-ci , mais elle est un peu verbeuse et vous oblige à créer plusieurs objets.

Si vous devez d'abord créer un objet vide, choisissez l'une de ces deux solutions. Gardez à l'esprit que chaque fois que vous utilisez as, vous perdez votre sécurité.

Solution plus sûre

Le type de objectest sûr à l' intérieur getObject, ce qui signifie object.aqu'il sera de typestring | undefined

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object: Partial<Example> = {};
  object.a = 'one';
  object.b = 1;
  return object as Example;
}

Solution courte

Le type de objectn'est pas sûr à l' intérieur getObject, ce qui signifie object.aqu'il sera de type stringmême avant son affectation.

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object = {} as Example;
  object.a = 'one';
  object.b = 1;
  return object;
}
fregante
la source