Définition de type dans un littéral d'objet dans TypeScript

346

Dans les classes TypeScript, il est possible de déclarer des types pour les propriétés, par exemple:

class className {
  property: string;
};

Comment déclarer le type d'une propriété dans un objet littéral?

J'ai essayé le code suivant mais il ne compile pas:

var obj = {
  property: string;
};

J'obtiens l'erreur suivante:

Le nom «chaîne» n'existe pas dans la portée actuelle

Suis-je en train de faire quelque chose de mal ou est-ce un bug?

Dace Zarina
la source

Réponses:

424

Vous êtes assez proche, il vous suffit de remplacer le =par un :. Vous pouvez utiliser un type d'objet littéral (voir la section 3.5.3 des spécifications) ou une interface. L'utilisation d'un littéral de type d'objet est proche de ce que vous avez:

var obj: { property: string; } = { property: "foo" };

Mais vous pouvez également utiliser une interface

interface MyObjLayout {
    property: string;
}

var obj: MyObjLayout = { property: "foo" };
Brian Terlson
la source
14
L'option DRY (Don't Repeat Yourself) de @Rick Love avec l'opérateur de distribution semble beaucoup plus nette. Juste des commentaires, pas un vote négatif ...
spechter
Je sais que cela a été répondu il y a un certain temps, mais je me trompe dans les hypothèses que des problèmes peuvent survenir lorsque la classe a des méthodes ainsi que des propriétés? Étant donné que la classe n'est pas initialisée et que nous n'affectons que des propriétés, l'appel d'une méthode sur la classe provoquera une exception nulle. Fondamentalement, l'objet que nous créons ne fait qu'agir en tant que classe parce que nous assignons son type, mais ce n'est pas réellement une instance de cette classe. C'est-à-dire que nous devons créer la classe avec le «nouveau» mot-clé. Et nous revenons alors au carré 1, car nous ne pouvons pas faire quelque chose comme new class () {prop: 1}; en TS comme on peut en C #, par exemple.
DeuxAlpha
@DeuxAlpha Ceci est assigné à une interface , qui ne peut pas avoir de méthodes. Je ne pense pas que vous puissiez assigner à une classe comme celle-ci.
Mog0
lien vers les spécifications serait bien :)
ahong
@DeuxAlpha, cela crée un objet littéral, pas un objet de classe. Une interface peut également avoir des méthodes. Si votre interface définit une méthode, le littéral objet doit également la définir - elle ne sera pas nulle. Le littéral objet doit remplir tout ce que l'interface définit ou le système de type affichera une erreur.
Rick Love
291

Mise à jour 2019-05-15 (modèle de code amélioré comme alternative)

Après de nombreuses années à utiliser constet à bénéficier d'un code plus fonctionnel, je recommanderais de ne pas utiliser ce qui suit dans la plupart des cas. (Lors de la construction d'objets, forcer le système de types dans un type spécifique au lieu de le laisser déduire des types est souvent une indication que quelque chose ne va pas).

Au lieu de cela, je recommanderais d'utiliser des constvariables autant que possible, puis de composer l'objet comme étape finale:

const id = GetId();
const hasStarted = true;
...
const hasFinished = false;
...
return {hasStarted, hasFinished, id};
  • Cela tapera correctement tout sans avoir besoin de taper explicitement.
  • Il n'est pas nécessaire de retaper les noms des champs.
  • Cela conduit au code le plus propre de mon expérience.
  • Cela permet au compilateur de fournir plus de vérification d'état (par exemple, si vous retournez à plusieurs endroits, le compilateur s'assurera que le même type d'objet est toujours retourné - ce qui vous encourage à déclarer la valeur de retour entière à chaque position - donnant un résultat parfaitement clair intention de cette valeur).

Ajout 2020-02-26

Si vous avez réellement besoin d'un type que vous pouvez initialiser paresseusement: marquez qu'il s'agit d'un type d'union annulable (null ou Type). Le système de type vous empêchera de l'utiliser sans vous assurer au préalable qu'il a une valeur.

Dans tsconfig.json, assurez-vous d'activer les vérifications nulles strictes:

"strictNullChecks": true

Ensuite, utilisez ce modèle et autorisez le système de type à vous protéger contre les accès nuls / non définis accidentels:



const state = {
    instance: null as null | ApiService,
    // OR
    // instance: undefined as undefined | ApiService,

};

const useApi = () => {
    // If I try to use it here, the type system requires a safe way to access it

    // Simple lazy-initialization 
    const api = state?.instance ?? (state.instance = new ApiService());
    api.fun();

    // Also here are some ways to only access it if it has value:

    // The 'right' way: Typescript 3.7 required
    state.instance?.fun();

    // Or the old way: If you are stuck before Typescript 3.7
    state.instance && state.instance.fun();

    // Or the long winded way because the above just feels weird
    if (state.instance) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans way
    if (state.instance != null) { state.instance.fun(); }

    // Or the I came from C and can't check for nulls like they are booleans 
    // AND I was told to always use triple === in javascript even with null checks way
    if (state.instance !== null && state.instance !== undefined) { state.instance.fun(); }
};

class ApiService {
    fun() {
        // Do something useful here
    }
}

Ne faites pas ce qui suit dans 99% des cas:

Mise à jour 2016-02-10 - Pour gérer TSX (Merci @Josh)

Utilisez l' asopérateur pour TSX.

var obj = {
    property: null as string
};

Un exemple plus long:

var call = {
    hasStarted: null as boolean,
    hasFinished: null as boolean,
    id: null as number,
};

Réponse originale

Utilisez l'opérateur de transtypage pour rendre cette opération succincte (en transtypant null au type souhaité).

var obj = {
    property: <string> null
};

Un exemple plus long:

var call = {
    hasStarted: <boolean> null,
    hasFinished: <boolean> null,
    id: <number> null,
};

C'est bien mieux que d'avoir deux parties (une pour déclarer les types, la seconde pour déclarer les valeurs par défaut):

var callVerbose: {
    hasStarted: boolean;
    hasFinished: boolean;
    id: number;
} = {
    hasStarted: null,
    hasFinished: null,
    id: null,
};
Rick Love
la source
10
Si vous utilisez TSX (TS avec JSX), vous ne pouvez pas utiliser la dénomination de l'équerre, donc ces lignes deviennent quelque chose comme property: null as stringoù la différence importante est l' asopérateur.
Josh
2
@RickLove Cela limite-t-il réellement le type de la variable objet, ou est-ce seulement un moyen de spécifier les types lorsqu'ils sont attribués? En d'autres termes, après avoir attribué à la variable calldans votre deuxième exemple, pouvez-vous lui attribuer un type complètement différent?
Daniel Griscom
3
Cela devrait être une réponse
Drew R
4
Ca ne fonctionne pas. Error:(33, 15) TS2352:Type 'null' cannot be converted to type 'string'.
slideshowp2
2
Est-ce simplement un abus d'une caractéristique de la langue ou est-ce réellement Legit? Pourriez-vous fournir un lien pour la lecture de plus de documents officiels? Merci!
Qwerty
31

Je suis surpris que personne n'ait mentionné cela, mais vous pouvez simplement créer une interface appelée ObjectLiteral, qui accepte des key: valuepaires de type string: any:

interface ObjectLiteral {
  [key: string]: any;
}

Ensuite, vous l'utiliseriez, comme ceci:

let data: ObjectLiteral = {
  hello: "world",
  goodbye: 1,
  // ...
};

Un bonus supplémentaire est que vous pouvez réutiliser cette interface autant de fois que vous le souhaitez, sur autant d'objets que vous le souhaitez.

Bonne chance.

LogicalBranch
la source
3
C'est une mauvaise idée, cela rend le système de type sans valeur. Le but de dactylographier est de permettre au système de dactylographie d'aider à prévenir les erreurs et également de fournir une meilleure fonctionnalité de saisie semi-automatique dans l'outillage - cela désactive essentiellement tous les avantages de Typescript. Il serait préférable de ne pas utiliser d'interface dans l'exemple ci-dessus.
Rick Love
1
@RickLove Je suis fortement en désaccord. Cela a été exceptionnellement utile lorsque plusieurs propriétés sont facultatives, mais nous voulons toujours définir clairement tous les types à l'intérieur (par exemple, contenant des arguments pour une fonction). Cela pourrait être simplement considéré comme du sucre syntaxique et fonctionne littéralement comme un opérateur d'étalement pour les propriétés des interfaces .
CPHPython
@CPHPython pourquoi ne pas simplement spécifier les paramètres facultatifs avec leurs types spécifiques? La seule fois où cela aurait du sens, c'est si les noms ne sont pas connus au moment du code (c'est-à-dire qu'ils proviennent d'une base de données ou d'une source externe). Aussi pour les combinaisons complexes d'arguments, les types d'union fonctionnent très bien. Si vous codez par rapport à des noms spécifiques, ils doivent être définis si possible. S'il ne s'agit que de données qui n'affectent pas la logique, alors bien sûr, laissez-les hors du système de type.
Rick Love
1
@RickLove "les noms ne sont pas connus au moment du code" -> ceci est un bon exemple pratique, les valeurs de ces types de clés sont connues et elles sont toutes les mêmes (par exemple chaîne ). Gardez à l'esprit que je ne préconisais que l'utilisation de la [key: string]partie, pas de la partie quelconque comme définition du type de la valeur ... Cela supprimerait en effet l'utilité des types.
CPHPython
1
Comment pourrais-je procéder si je souhaite autoriser l'ajout de chaînes et de nombres, mais pas d'autres types?
DonkeyBanana
14

Si vous essayez d'écrire une annotation de type, la syntaxe est:

var x: { property: string; } = ...;

Si vous essayez d'écrire un littéral objet, la syntaxe est:

var x = { property: 'hello' };

Votre code essaie d'utiliser un nom de type dans une position de valeur.

Ryan Cavanaugh
la source
10
Votre var x: { property: string; } = ...;est une allumeuse! J'espérais que les points de suspension étaient une syntaxe valide pour être un raccourci var x: { property: string; } = { property: 'hello' };.
Jason Kleban
10

Dans TypeScript, si nous déclarons un objet, nous utilisons la syntaxe suivante:

[access modifier] variable name : { /* structure of object */ }

Par exemple:

private Object:{ Key1: string, Key2: number }
mukund patel
la source
7

Si vous essayez d'ajouter des typages à un littéral d'objet déstructuré , par exemple dans les arguments d'une fonction, la syntaxe est:

function foo({ bar, baz }: { bar: boolean, baz: string }) {
  // ...
}

foo({ bar: true, baz: 'lorem ipsum' });
Egor
la source
5

Si vos propriétés ont le même type, vous pouvez utiliser un type d'utilitaire prédéfini Record:

type Keys = "property" | "property2"

var obj: Record<Keys, string> = {
  property: "my first prop",
  property2: "my second prop",
};

Vous pouvez bien sûr aller plus loin et définir un type personnalisé pour les valeurs de votre propriété:

type Keys = "property" | "property2"
type Value = "my prop" | "my other allowed prop"

var obj: Record<Keys, Value> = {
  property: "my prop",
  property2: "my second prop", // TS Error: Type '"my second prop"' is not assignable to type 'Value'.
};
strelettes
la source
2
Exactement ce dont j'avais besoin. Merci! Typographie pour la victoire!
Audacite
J'utiliserais également letau lieu d' varici.
Fwd079
3
// Use ..

const Per = {
  name: 'HAMZA',
  age: 20,
  coords: {
    tele: '09',
    lan: '190'
  },
  setAge(age: Number): void {
    this.age = age;
  },
  getAge(): Number {
    return age;
  }
};
const { age, name }: { age: Number; name: String } = Per;
const {
  coords: { tele, lan }
}: { coords: { tele: String; lan: String } } = Per;

console.log(Per.getAge());
Hamza Ibrahim
la source
3
Hé et bienvenue à SO! Pouvez-vous développer votre réponse, il serait utile d'expliquer comment / pourquoi cela fonctionne.
party-ring
1

Dans votre code:

var obj = {
  myProp: string;
};

Vous créez en fait un littéral objet et affectez la chaîne de variables à la propriété myProp. Bien que très mauvaise pratique, ce serait en fait du code TS valide (ne l'utilisez pas!):

var string = 'A string';

var obj = {
  property: string
};

Cependant, ce que vous voulez, c'est que le littéral de l'objet soit tapé. Cela peut être réalisé de différentes manières:

Interface:

interface myObj {
    property: string;
}

var obj: myObj = { property: "My string" };

Type personnalisé:

type myObjType = {
    property: string
};

var obj: myObjType = { property: "My string" };

Opérateur de casting:

var obj = {
    myString: <string> 'hi',
    myNumber: <number> 132,
};

Type d'objet littéral:

var obj: { property: string; } = { property: "Mystring" };
Willem van der Veen
la source