Accueil / javascript / 1-js / 05-data-types / 10-destructuring-assignment / article.md

article.md

L'affectation par décomposition

Les deux structures de données les plus utilisées en JavaScript sont Object et Array.

  • Les objets nous permettent de créer une seule entité qui stocke les éléments de données par clé.
  • Les tableaux nous permettent de rassembler des éléments de données dans une liste ordonnée.

Mais lorsque nous transmettons ceux-ci à une fonction, il se peut que celle-ci n'ait pas besoin d'un objet / tableau dans son ensemble, mais plutôt de morceaux individuels.

*L'affectation par décomposition* est une syntaxe spéciale qui nous permet de "décompresser" des tableaux ou des objets dans un ensemble de variables, ce qui est parfois plus pratique.

La décomposition fonctionne également très bien avec des fonctions complexes comportant de nombreux paramètres, valeurs par défaut, etc.

Décomposition d'un tableau

Voici un exemple de la façon dont un tableau est décomposé en variables :

// nous avons un tableau avec le prénom et le nom
let arr = ["John", "Smith"]


// l'affectation par décomposition
// sets firstName = arr[0]
// and surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname);  // Smith

Maintenant, nous pouvons travailler avec des variables plutôt que des parties du tableau.

Cela a l'air pas mal quand c'est combiné avec split ou tout autre méthode de renvoi de tableau :

let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname);  // Smith

Comme vous pouvez le voir, la syntaxe est simple. Il y a cependant plusieurs détails particuliers. Voyons plus d'exemples, pour mieux le comprendre.

Le rest '...'

Habituellement, si le tableau est plus long que la liste à gauche, les éléments "supplémentaires" sont omis.

Par exemple, ici, seuls deux éléments sont pris et le reste est simplement ignoré :

let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar
// Les autres éléments ne sont affectés nulle part

Si nous souhaitons également rassembler tout ce qui suit, nous pouvons ajouter un paramètre supplémentaire qui permet d'obtenir "le reste" à l'aide de trois points "..." :

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];


// reste est un tableau d'éléments, à partir du 3ème
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

La valeur de rest est le tableau des éléments du tableau restants.

Nous pouvons utiliser n’importe quel autre nom de variable à la place de rest, assurez-vous simplement qu’il a trois points devant lui et soit placé en dernier dans l’affectation par décomposition.

let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// maintenant titles = ["Consul", "of the Roman Republic"]

Les valeurs par défaut

Si le tableau est plus court que la liste des variables à gauche, il n'y aura aucune erreur. Les valeurs absentes sont considérées comme non définies :


let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

Si nous voulons qu'une valeur "par défaut" remplace la valeur manquante, nous pouvons la fournir en utilisant = :


// les valeurs par défaut
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (depuis le tableau)
alert(surname); // Anonymous (valeur par défaut)

Les valeurs par défaut peuvent être des expressions plus complexes ou même des appels de fonction. Ils ne sont évalués que si la valeur n'est pas fournie.

Par exemple, nous utilisons ici la fonction prompt pour deux valeurs par défaut :

// ne demande que le nom de famille
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];

alert(name);    // Julius (depuis le tableau)
alert(surname); // saisi par l'utilisateur

Notez que le prompt ne s'exécutera que pour la valeur manquante (surname).

Décomposition d'object

L'affectation par décomposition fonctionne également avec les objets.

La syntaxe de base est la suivante :

let {var1, var2} = {var1:…, var2:…}

Nous devrions avoir un objet existant sur le côté droit, que nous voulons diviser en variables. La partie gauche contient un "modèle" de type objet pour les propriétés correspondantes. Dans le cas le plus simple, c'est une liste de noms de variables dans {...}.

Par exemple :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};


let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Les propriétés options.title, options.width et options.height sont affectées aux variables correspondantes.

L'ordre n'a pas d'importance. Cela fonctionne aussi :

// changé l'ordre dans let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }

Le pattern à gauche peut être plus complexe et spécifier le mapping entre propriétés et variables.

Si nous voulons affecter une propriété à une variable portant un autre nom, par exemple, options.width pour aller dans la variable nommée w, alors nous pouvons la définir en utilisant deux points :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};


// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

Les deux points montrent "quoi: va où". Dans l'exemple ci-dessus, la propriété width est définie sur w, la propriété height est définie sur h et le title est attribué au même nom.

Pour les propriétés potentiellement manquantes, nous pouvons définir les valeurs par défaut à l'aide de "=", comme ceci :

let options = {
  title: "Menu"
};


let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Comme pour les tableaux ou les paramètres de fonction, les valeurs par défaut peuvent être des expressions ou même des appels de fonction. Elles seront évaluées si la valeur n'est pas fournie.

Le code prompt ci-dessous demande la width, mais pas le title.

let options = {
  title: "Menu"
};


let {width = prompt("width?"), title = prompt("title?")} = options;

alert(title);  // Menu
alert(width);  // saisi par l'utilisateur

Nous pouvons également combiner les deux points et l'égalité :

let options = {
  title: "Menu"
};


let {width: w = 100, height: h = 200, title} = options;

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

Si nous avons un objet complexe avec de nombreuses propriétés, nous pouvons extraire que ce dont nous avons besoin :

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// extraire uniquement le titre en tant que variable
let { title } = options;

alert(title); // Menu

Le pattern rest "..."

Et si l'objet a plus de propriétés que de variables ? Peut-on en prendre puis assigner le "rest" quelque part ?

Nous pouvons utiliser le modèle rest, comme nous l'avons fait avec les tableaux. Il n'est pas pris en charge par certains navigateurs plus anciens (IE, utilisez Babel pour le polyfiller), mais fonctionne avec les modernes.

Cela ressemble à ceci :

let options = {
  title: "Menu",
  height: 200,
  width: 100
};


// title = propriété nommée title
// rest = objet avec le reste des propriétés
let {title, ...rest} = options;

// maintenant title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100

Décomposition imbriquée

Si un objet ou un tableau contient d'autres objets et tableaux imbriqués, nous pouvons utiliser des modèles à gauche plus complexes pour extraire des parties plus profondes.

Dans le code ci-dessous, options a un autre objet dans la propriété size et un tableau dans la propriété items. Le modèle à gauche de l'affectation a la même structure pour en extraire des valeurs :

let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true
};

// affectation par décomposition divisée sur plusieurs lignes pour la clarté
let {
  size: { // mettre la taille ici
    width,
    height
  },
  items: [item1, item2], // attribuer des éléments ici
  title = "Menu" // non présent dans l'objet (la valeur par défaut est utilisée)
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

Toutes les propriétés de l'objet options, à l'exception de extra qui est absente dans la partie gauche, sont affectés aux variables correspondantes :

Finnalement nous avons width, height, item1, item2 et title à partir de la valeur par défaut.

Notez qu'il n'y a pas de variables pour size et items, car nous prenons leur contenu à la place.

Paramètres de fonction intelligente

Il peut arriver qu'une fonction ait plusieurs paramètres, dont la plupart sont facultatifs. C’est particulièrement vrai pour les interfaces utilisateur. Imaginez une fonction qui crée un menu. Il peut avoir une largeur, une hauteur, un titre, une liste d’articles, etc.

Voici une mauvaise façon d’écrire ce genre de fonction :

function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
  // ...
}

Dans la vraie vie, le problème est de savoir comment retenir l'ordre des arguments. Habituellement, les IDE essaient de nous aider, surtout si le code est bien documenté, mais quand même… Un autre problème est de savoir comment appeler une fonction lorsque la plupart des paramètres sont corrects par défaut.

Comme ceci ?

// undefined où les valeurs par défaut sont adéquates
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])

C’est moche. Et devient illisible lorsque nous traitons plus de paramètres.

La décomposition vient à la rescousse !

Nous pouvons passer des paramètres sous forme d'objet, et la fonction les décomposent immédiatement en variables :

// on passe un objet à fonction
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

// ...et il est immédiatement étendu aux variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
  // title, items – pris des options,
  // width, height – défauts utilisés
  alert( `${title} ${width} ${height}` ); // My Menu 200 100
  alert( items ); // Item1, Item2
}

showMenu(options);

Nous pouvons également utiliser une décomposition plus complexe avec des objets imbriqués et des mappings de deux points :

let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};


function showMenu({
  title = "Untitled",
  width: w = 100,  // width va à w
  height: h = 200, // height va à h
  items: [item1, item2] // items premier élément va à item1, deuxième à item2
}) {

  alert( `${title} ${w} ${h}` ); // My Menu 100 200
  alert( item1 ); // Item1
  alert( item2 ); // Item2
}

showMenu(options);

La syntaxe complète est la même que pour une affectation par décomposition :

function({
  incomingProperty: varName = defaultValue
  ...
})

Ensuite, pour un objet de paramètres, il y aura une variable varName pour la propriété incomingProperty, avec defaultValue par défaut.

Veuillez noter qu'une telle déstructuration suppose que showMenu() a un argument. Si nous voulons toutes les valeurs par défaut, alors nous devrions spécifier un objet vide :

showMenu({}); // ok, toutes les valeurs par défaut

showMenu(); // cela donnerait une erreur

Nous pouvons résoudre ce problème en faisant de {} la valeur par défaut pour l'objet entier de paramètres :

function showMenu({ title = "Menu", width = 100, height = 200 } = {}) {
  alert( `${title} ${width} ${height}` );
}

showMenu(); // Menu 100 200

Dans le code ci-dessus, la totalité des arguments objet est {} par défaut, il y a donc toujours quelque chose à décomposer.

Résumé

  • L'affectation par décomposition permet de mapper instantanément un objet ou un tableau sur de nombreuses variables.
  • La syntaxe complète de l'objet :
    let {prop : varName = default, ...rest} = object
    

Cela signifie que la propriété prop doit aller dans la variable varName et que, si aucune propriété de ce type n'existe, la valeur default doit être utilisée.

Les propriétés d'objet sans mappage sont copiées dans l'objet rest.

  • La syntaxe complète du tableau :
    let [item1 = default, item2, ...rest] = array
    

Le premier item va à item1; le second passe à item2, tout le reste du tableau correspond au rest.

  • Il est possible d'extraire des données de tableaux / objets imbriqués, pour cela le côté gauche doit avoir la même structure que le droit.