Paramètres du reste et décomposition : les deux visages de ... en JavaScript & TypeScript

Je suis récemment tombé sur ce petit utilitaire en refactorisant un routeur dans notre base de code :

export function docsPath(...segments: string[]): string {
  const path = segments.filter(Boolean).join("/");
  return path ? `${DOCS_BASE}/${path}` : DOCS_BASE;
}

Ce qui m'a fait m'arrêter, c'est ...segments. J'avais vu l'opérateur ... une centaine de fois pour copier des objets ({ ...props }) ou des tableaux ([...items]), mais je ne m'étais jamais vraiment demandé ce qu'il faisait à l'intérieur de la liste de paramètres d'une fonction. Il s'avère que ... porte deux casquettes différentes selon l'endroit où on le place — et comprendre la différence éclaire d'un coup une grande partie du code de tous les jours.

Le problème que résolvent les paramètres du reste

Imaginons que vous vouliez une fonction qui assemble des segments d'URL en un chemin. Combien de segments l'appelant va-t-il passer ? Vous ne le savez pas — parfois un, parfois trois, parfois aucun :

docsPath("guides");                 // "/docs/guides"
docsPath("guides", "my-slug");      // "/docs/guides/my-slug"
docsPath("customers", "acme", "x"); // "/docs/customers/acme/x"
docsPath();                         // "/docs"

Avant les paramètres du reste, il fallait s'appuyer sur le fameux objet arguments, peu pratique et qui n'est pas un vrai tableau :

function docsPath() {
  // `arguments` ressemble à un tableau, mais on ne peut pas appeler .filter ou .join directement dessus
  const segments = Array.prototype.slice.call(arguments);
  // ...
}

Les paramètres du reste remplacent tout cela par une seule syntaxe élégante.

Ce que ...segments signifie vraiment

Lorsque ... apparaît dans la liste de paramètres d'une fonction, c'est un paramètre du reste (rest parameter). Il dit :

« Rassemble tous les arguments passés par l'appelant, quel que soit leur nombre, dans un seul vrai tableau nommé segments. »

function docsPath(...segments: string[]) {
  // segments est un véritable Array<string>
}

docsPath("a", "b", "c"); // segments === ["a", "b", "c"]
docsPath();              // segments === []

Comme segments est un vrai tableau, vous disposez gratuitement de toutes les méthodes de tableau — ce qui est exactement ce que l'utilitaire utilise :

const path = segments.filter(Boolean).join("/");

Quelques règles à retenir :

  • Une fonction ne peut avoir qu'un seul paramètre du reste.

  • Il doit être en dernier dans la liste de paramètres (il récupère « le reste »).

  • Vous pouvez le combiner avec des paramètres ordinaires :

function logEvent(level: string, ...messages: string[]) {
  console.log(`[${level}]`, ...messages);
}

logEvent("info", "user", "logged", "in");
// level === "info"
// messages === ["user", "logged", "in"]

Décortiquons l'utilitaire

Suivons docsPath pas à pas avec DOCS_BASE = "/docs" :

docsPath("guides", "my-slug");
// 1. segments = ["guides", "my-slug"]   ← le paramètre du reste rassemble les arguments
// 2. .filter(Boolean)  → ["guides", "my-slug"]   (supprime "", undefined, null)
// 3. .join("/")        → "guides/my-slug"
// 4. path est truthy   → "/docs/guides/my-slug"

docsPath();
// 1. segments = []
// 2. .filter(Boolean) → []
// 3. .join("/")       → ""
// 4. path est falsy   → "/docs"   (uniquement la base)

L'étape .filter(Boolean) est une jolie précaution défensive. Elle permet aux appelants de passer des segments conditionnels ou potentiellement vides sans produire de doubles barres obliques :

const slug = maybeUndefined();        // peut être undefined ou ""
docsPath("guides", slug);             // reste "/docs/guides", jamais "/docs/guides/"

(Boolean utilisé comme callback élimine toutes les valeurs falsy : "", undefined, null, 0, NaN, false.)

L'autre visage : la décomposition

Voici le rebondissement. Le même symbole ... fait le travail inverse lorsqu'il apparaît dans un appel de fonction ou un littéral de tableau/objet. Là, c'est l'opérateur de décomposition (spread) : il prend un itérable existant et le déploie en éléments individuels.

const parts = ["customers", "acme"];

docsPath(...parts);
// équivalent à docsPath("customers", "acme")

Donc :

  • Rest (dans une liste de paramètres) : rassemble plusieurs valeurs dans un tableau.

  • Spread (dans un appel / un littéral) : disperse un tableau en plusieurs valeurs.

Ce sont des images miroir. La façon la plus claire de le voir est d'utiliser les deux ensemble :

function docsPath(...segments: string[]) {   // rest : rassembler
  return "/docs/" + segments.join("/");
}

const parts = ["a", "b", "c"];
docsPath(...parts);                           // spread : disperser

La décomposition apparaît aussi dans de nombreux autres contextes :

// Copier / fusionner des tableaux
const merged = [...arr1, ...arr2];

// Copier / fusionner des objets
const updated = { ...user, name: "New Name" };

// Passer un tableau là où des arguments individuels sont attendus
Math.max(...[3, 1, 4, 1, 5]); // 5

Un modèle mental rapide

Si vous ne deviez retenir qu'une seule chose, retenez l'endroit où se trouve le ... :

EmplacementNomCe qu'il faitListe de paramètres d'une fonctionRestRassemble les arguments dans un tableauAppel de fonctionSpreadDéploie un tableau en argumentsLittéral de tableau [...]SpreadInsère les éléments dans un nouveau tableauLittéral d'objet {...}SpreadInsère les propriétés dans un nouvel objet

Même syntaxe, directions opposées — rassembler ou disperser.

Notes TypeScript

En TypeScript, on type un paramètre du reste comme un tableau, et chaque argument rassemblé doit correspondre au type des éléments :

function docsPath(...segments: string[]): string { /* ... */ }

docsPath("a", "b");   // ✅
docsPath("a", 42);    // ❌ L'argument de type 'number' n'est pas assignable au type 'string'

Vous pouvez même typer des paramètres du reste hétérogènes avec des types tuple, ce qui est la manière dont sont construits les émetteurs d'événements typés et les signatures de style console.log :

function emit(event: string, ...args: [id: number, ok: boolean]) {}

emit("done", 1, true);  // ✅
emit("done", 1);        // ❌ il manque le booléen

À retenir

  • ...nom dans une liste de paramètres est un paramètre du reste — il transforme « un nombre quelconque d'arguments » en un vrai tableau sur lequel vous pouvez faire filter, map et join.

  • ...valeur dans un appel ou un littéral est l'opérateur de décomposition — il déploie un tableau/objet en éléments individuels.

  • Ce sont des inverses : rest rassemble, spread disperse.

  • Les paramètres du reste sont le remplaçant moderne et sûr au niveau des types de l'ancien objet arguments, et ils rendent les petits utilitaires comme docsPath propres et flexibles.

Une toute petite syntaxe, mais une fois que l'on voit les deux visages de ..., une quantité surprenante de code JavaScript et TypeScript idiomatique cesse de ressembler à de la magie.