01 Interpolation de Chaînes#
Dans les versions antérieures de JavaScript, si nous voulions créer dynamiquement des chaînes, nous devions utiliser l'opérateur d'addition (+) :
const userName = 'Mai';
const dynamicString = 'hello ' + userName + '!';
Cela fonctionne bien, mais cela peut sembler assez maladroit parfois, et peut conduire à des bugs (par exemple, oublier l'espace dans Hello ).
Le JavaScript moderne nous permet d'intégrer des variables et d'autres expressions directement dans les chaînes :
const dynamicString = `hello ${userName}!`;
Pour utiliser l'interpolation de chaînes, nous devons utiliser des backticks (`).
Les chaînes créées avec des backticks sont connues sous le nom de "template strings". Pour la plupart, elles fonctionnent comme n'importe quelle autre chaîne, mais elles ont ce super-pouvoir : elles peuvent intégrer des segments dynamiques.
Nous créons un segment dynamique dans notre chaîne en écrivant ${}. Tout ce qui est placé entre les accolades sera évalué comme une expression JavaScript.
Cela signifie que nous pouvons faire des choses sophistiquées comme ceci :
const age = 7;
console.log(`Next year, you'll be ${age + 1} years old.`);
Cela crée la chaîne "Next year, you'll be 8 years old.".
02 Fonctions Fléchées#
Historiquement, les fonctions en JavaScript ont été écrites en utilisant le mot-clé function :
function exclaim(string) {
return string + '!';
}
En 2015, le langage a reçu une syntaxe alternative pour créer des fonctions : les fonctions fléchées. Elles ressemblent à ceci :
const exclaim = string => string + '!';
Les fonctions fléchées sont inspirées des fonctions lambda d'autres langages de programmation fonctionnelle. Leur principal avantage est qu'elles sont beaucoup plus courtes et plus propres. Réduire l'"encombrement des fonctions" peut sembler un avantage insignifiant, mais cela peut vraiment aider à améliorer la lisibilité lors du travail avec des fonctions anonymes. Par exemple :
const arr = ['hey', 'ho', 'let\'s go'];
// This:
arr
.map(function(string) {
return string + '!'
})
.join(' ');
// …Becomes this:
arr
.map(string => string + '!')
.join(' ');
Règles des fonctions fléchées#
Les fonctions fléchées peuvent sembler simples au premier coup d'œil, mais il y a quelques "pièges" à connaître. Il est très courant que les gens se fassent piéger par certaines de ces règles.
Forme courte vs. forme longue#
Il existe deux types de fonctions fléchées : la forme courte et la forme longue.
Voici la forme courte :
const add1 = n => n + 1;
Et voici la forme longue :
const add1 = n => {
return n + 1;
};
Nous optons pour la forme longue en ajoutant des accolades ({ }) autour du corps de la fonction.
La différence fondamentale entre les deux formes est la suivante :
- Le corps de la fonction de forme courte doit être une seule expression. Cette expression sera automatiquement retournée.
- Le corps de la fonction de forme longue peut contenir un nombre d'instructions. Nous devons spécifier manuellement le retour.
Avec la forme courte, notre corps de fonction consiste en une seule expression, et cette expression sera retournée. Ceci est parfois appelé un retour "implicite", car il n'y a pas de mot-clé return.
Quand nous ajoutons les accolades ({ }), nous créons un bloc d'instructions. Nous pouvons mettre autant d'instructions que nous voulons là-dedans. Nous devons spécifier ce qui doit être retourné en utilisant une instruction return.
Fait intéressant, si nous essayons d'ajouter le mot-clé return à la syntaxe de forme courte, nous obtiendrons une erreur de syntaxe :
const add1 = n => return n + 1;
// Uncaught SyntaxError: Unexpected token 'return'
Pourquoi est-ce une erreur ? Eh bien, return n + 1 est une instruction, pas une expression. La forme courte nécessite une expression. Nous ne sommes pas autorisés à mettre une instruction là.
Parfois, nous verrons des fonctions fléchées écrites comme ceci :
const shout = sentence => (
sentence.toUpperCase()
);
Que signifie-t-il quand nous utilisons des parenthèses (( )) au lieu d'accolades ({ }) ?
Les parenthèses peuvent être ajoutées pour nous aider avec le formatage. En ajoutant des parenthèses, nous sommes capables de pousser l'expression retournée vers une nouvelle ligne. Et donc, nous utilisons toujours la structure de retour "implicite" de forme courte, mais nous la restructurons pour la rendre plus lisible.
Pour le dire autrement, ces deux fonctions sont identiques :
// On multiple lines, with parens:
const shoutWithParens = sentence => (
sentence.toUpperCase()
);
// Or, in a single line without parens:
const shoutWithoutParens = sentence => sentence.toUpperCase();
Parenthèses de paramètres optionnelles#
Si une fonction fléchée prend un seul paramètre, les parenthèses sont optionnelles :
// This is valid:
const logUser = user => {
console.log(user);
}
// This is also valid:
const logUser = (user) => {
console.log(user);
}
Les parenthèses sont obligatoires si nous avons plus d'un paramètre :
const updateUser = (user, properties, isAdmin) => {
if (!isAdmin) {
throw new Error('Not authorized')
}
user.setProperties(properties);
}
Les parenthèses sont également obligatoires si nous n'avons aucun paramètre :
const sayHello = () => console.log('Hello!')
Retour implicite d'objets#
Supposons que nous ayons une fonction qui retourne un objet :
function makeObject() {
return {
hi: 5,
};
}
Quelque chose d'assez drôle se produit quand nous essayons de la convertir en fonction fléchée de forme courte :
const makeObject = () => { hi: 5 };
Il y a deux façons d'interpréter ce code :
- Une fonction fléchée de forme courte qui retourne un objet, { hi: 5 }
- Une fonction fléchée de forme longue avec une seule instruction, hi: 5
Le problème est que les accolades ({}) servent deux objectifs en JavaScript : elles sont utilisées pour la notation d'objet, mais elles sont aussi utilisées pour créer des blocs, comme dans les instructions if.
Quand les accolades suivent une flèche (=>), le moteur JS assume que nous créons un nouveau bloc, et donc nous obtiendrons une erreur de syntaxe, puisque hi: 5 n'est pas une instruction JS valide.
Si nous voulons retourner implicitement un objet, nous devons l'envelopper dans des parenthèses :
const makeObject = () => ({ hi: 5 });
En JavaScript, les parenthèses peuvent être ajoutées autour de n'importe quelle expression, pour changer son ordre d'évaluation. Dans ce cas, nous ne nous soucions pas de l'ordre d'évaluation, nous devons juste clarifier que nous essayons de passer une expression.
De même, il est courant d'envelopper l'expression de forme courte dans des parenthèses quand elle est trop longue pour tenir sur une seule ligne :
const matchedItem = items.find(item => (
item.color === 'red' && item.size === 'large'
));
Nous pouvons envelopper n'importe quelle expression dans des parenthèses, mais nous ne pouvons pas envelopper un bloc dans des parenthèses. Et donc, quand nous enveloppons les caractères {} dans des parenthèses, le moteur peut comprendre que nous essayons de créer un objet, pas un bloc.
03 Destructuration d'Objets#
La destructuration d'objets offre un moyen élégant d'extraire certaines variables d'un objet.
Voici un exemple rapide :
const user = {
name: 'François Bouchard',
city: 'Saint-Louis-du-Ha! Ha!',
province: 'Québec',
country: 'Canada',
postalCode: 'A1B 2C3',
};
const { name, country } = user;
console.log(name); // 'François Bouchard'
console.log(country); // 'Canada'
Ceci est effectivement la même chose que de faire ceci :
const name = user.name;
const country = user.country;
Nous pouvons extraire autant ou aussi peu de valeurs que nous voulons.
Renommer les valeurs extraites#
Considérez cette situation :
const name = 'Hello!';
const user = { name: 'Marie-Hélene Pelletier' };
const { name } = user;
// Uncaught SyntaxError:
// Identifier 'name' has already been declared
Nous avons essayé de destructurer la propriété name dans sa propre variable, mais nous rencontrons un problème : il y a déjà une variable appelée name !
Dans ces cas, nous pouvons renommer la valeur pendant que nous la déballons :
const name = 'Hello!';
const user = { name: 'Marie-Hélene Pelletier' };
const { name: userName } = user;
console.log(name); // 'Hello!'
console.log(userName); // 'Marie-Hélene Pelletier'
Valeurs par défaut#
Voici une question : que se passe-t-il si nous essayons de destructurer une clé d'un objet qui n'est pas définie ?
const user = { name: 'Marie-Hélene Pelletier' };
const { status } = user;
console.log(status); // ???
Eh bien, user.status n'est pas défini, et donc status sera défini à undefined.
Si nous voulons, nous pouvons définir une valeur par défaut en utilisant l'opérateur d'assignation :
const { status = 'idle' } = user;
Si l'objet user a une propriété status, cette valeur sera extraite et assignée à la nouvelle constante status. Sinon, status sera assigné à la chaîne "idle".
En d'autres termes, c'est une version moderne de ceci :
const status = typeof user.status === 'undefined'
? 'idle'
: user.status;
Destructuration des paramètres de fonction#
Supposons que nous ayons une fonction qui prend un objet comme premier paramètre :
function validateUser(user) {
if (typeof user.name !== 'string') {
return false;
}
if (user.password.length < 12) {
return false;
}
return true;
}
Si nous voulions, nous pourrions destructurer ces valeurs en haut de la fonction :
function validateUser(user) {
const { name, password } = user;
if (typeof name !== 'string') {
return false;
}
if (password.length < 12) {
return false;
}
return true;
}
En utilisant la destructuration de paramètres, nous pouvons faire cette destructuration directement dans les paramètres de la fonction :
function validateUser({ name, password }) {
if (typeof name !== 'string') {
return false;
}
if (password.length < 12) {
return false;
}
return true;
}
Ces 3 extraits de code sont équivalents, mais beaucoup de développeurs aiment utiliser la destructuration de paramètres de fonction. C'est particulièrement populaire en React pour destructurer les props dans nos composants.
Arguments nommés#
Certains langages ont des "arguments nommés", où le développeur peut choisir d'étiqueter les arguments qu'il passe dans une fonction.
JavaScript n'a pas cette fonctionnalité, et en conséquence, il peut être difficile de comprendre quel rôle jouent différents arguments.
Par exemple, supposons que vous travaillez sur une base de code, et vous tombez sur ce code :
trackSession(user.id, 5, null);
Pourquoi cette fonction reçoit-elle un 5 et un null ? C'est totalement mystérieux. Nous devrions chercher le fichier où cette fonction est définie pour comprendre ce que sont ces valeurs.
Supposons, cependant, que trackSession prenne un objet comme seul argument.
Alors cela ressemblerait à ceci :
trackSession({
userId: user.id,
version: 5,
additionalMetadata: null,
});
En fournissant un objet, nous devons effectivement étiqueter chaque valeur ! Cela fonctionne beaucoup comme les "arguments nommés" dans d'autres langages !
Quand j'écris une fonction, j'utiliserai souvent la destructuration de paramètres spécifiquement pour pouvoir passer un objet, plutôt qu'un tas d'arguments individuels. De cette façon, il est plus facile de comprendre ce que sont les données fournies.
Valeurs de paramètres par défaut#
Comme avec la destructuration d'objets typique, nous pouvons fournir des valeurs par défaut à utiliser pour les paramètres.
Voici un exemple rapide :
function sendApiRequest({ method = 'GET', numOfRetries }) {
// Stuff
}
Quand j'appelle cette fonction, je peux fournir ma propre valeur pour method :
sendApiRequest({ method: 'PUT', numOfRetries: 4 });
…Ou, si je veux qu'elle soit égale à GET, je peux l'omettre entièrement :
sendApiRequest({ numOfRetries: 4 });
Supposons que nous ajoutons des valeurs par défaut pour les deux propriétés dans notre fonction :
function sendApiRequest({ method = 'GET', numOfRetries = 5 }) {
// Stuff
}
Nous appelons ensuite cette fonction sans spécifier d'argument, puisque nous voulons utiliser toutes les valeurs par défaut :
sendApiRequest();
Malheureusement, nous obtenons une erreur :
Uncaught TypeError: Cannot read properties of undefined (reading 'method')
Voici le problème : nous destructurons les propriétés method et numOfRetries d'un objet, mais quand nous appelons la fonction sendApiRequest, nous ne fournissons pas d'objet.
Voici un autre exemple du même problème, qui pourrait le rendre plus clair :
function sendApiRequest(properties) {
// `properties` is undefined, and so we'll get the same
// error when we try and extract values from it:
const {
method = 'GET',
numOfRetries = 5
} = properties;
}
sendApiRequest();
Pour résoudre ce problème, nous pouvons définir une valeur par défaut pour le premier paramètre, l'objet dont nous destructurons :
function sendApiRequest(
{ method = 'GET', numOfRetries = 5 } = {}
) {
// Stuff
}
sendApiRequest(); // ✅ No problem!
Quand nous appelons sendApiRequest sans passer d'arguments, le premier paramètre est initialisé à un objet vide. Ensuite, les propriétés method et numOfRetries sont initialisées en utilisant leurs valeurs par défaut, puisqu'elles n'étaient pas définies dans cet objet vide.
Ces choses deviennent assez complexes. Vous n'avez certainement pas besoin de profiter de toute cette syntaxe moderne. Je voulais juste vous faire savoir que c'est un problème que vous pourriez rencontrer.
04 Raccourci de Valeur de Propriété#
Les objets en JavaScript moderne ont un petit truc astucieux dans leur manche. C'est une petite chose, mais si vous n'en êtes pas conscient, cela peut causer beaucoup de confusion.
Supposons que nous ayons le code suivant :
const name = 'Ahmed';
const age = 26;
const user = {
name: name,
age: age,
};
console.log(user);
// { name: 'Ahmed', age: 26 }
Nous créons un objet user avec deux propriétés, name et age. Nous assignons ces propriétés à des variables du même nom.
Cela semble un peu redondant, cependant, n'est-ce pas ? Nous assignons name à name, et assignons age à age.
Le JavaScript moderne nous donne un raccourci pratique que nous pouvons utiliser dans cette situation :
const name = 'Ahmed';
const age = 26;
const user = {
name,
age,
};
console.log(user);
// { name: 'Ahmed', age: 26 }
Ceci est connu sous le nom de raccourci de valeur de propriété. Lors de la création d'un objet, nous pouvons omettre les valeurs si elles partagent le même nom que la propriété.
Le résultat final est le même, mais c'est un peu plus concis.
05 Destructuration de Tableaux#
Supposons que nous ayons des données qui vivent dans un tableau, et nous voulons les extraire et les assigner à une variable.
Traditionnellement, cela a été fait en accédant à un élément par index, et en l'assignant une valeur avec une instruction d'assignation typique :
const fruits = ['apple', 'banana', 'cantaloupe'];
const firstFruit = fruits[0];
const secondFruit = fruits[1];
L'assignation de destructuration nous offre une façon plus agréable d'accomplir cette tâche :
const fruits = ['apple', 'banana', 'cantaloupe'];
const [firstFruit, secondFruit] = fruits;
La première fois que vous le voyez, ces crochets de tableau avant le = semblent assez sauvages.
Voici comment je pense à cela : quand les caractères [ ] sont utilisés après l'opérateur d'assignation (=), ils sont utilisés pour empaqueter des éléments dans un tableau. Quand ils sont utilisés avant, ils font l'opposé, et ils déballent des éléments d'un tableau :
// Packing 3 values into an array:
const array = [1, 2, 3];
// Unpacking 3 values from an array:
const [first, second, third] = array;
En fin de compte, ce n'est pas vraiment révolutionnaire, mais c'est un petit tour de passe-passe qui peut nettoyer les choses un peu.
Ignorer des éléments#
Que faire si nous voulions seulement saisir le deuxième élément, et pas le premier ? Pouvons-nous utiliser l'assignation de destructuration dans ce cas ?
En fait, nous pouvons :
const fruits = ['apple', 'banana', 'cantaloupe'];
const [, secondFruit] = fruits;
Nous avons supprimé firstFruit, mais gardé la virgule. Essentiellement, nous avons laissé un espace où la première variable était. Si vous êtes familier avec la notation musicale, vous pouvez y penser comme un silence.
Techniquement, nous pouvons laisser beaucoup d'espaces, en choisissant les éléments que nous voulons :
const fruits = [
'apple',
'banana',
'cantaloupe',
'durian',
'eggplant'
];
const [, secondFruit,,, fifthFruit] = fruits;
console.log(secondFruit); // 'banana'
console.log(fifthFruit); // 'eggplant'
C'est un petit truc intéressant avec l'assignation de destructuration, mais honnêtement je ne sais pas si c'est une bonne idée dans des cas complexes comme celui-ci. Il me semble plus simple d'utiliser la notation standard :
const secondFruit = fruits[1];
const fifthFruit = fruits[4];
06 Modules JavaScript#
Pendant longtemps, JavaScript n'avait pas de système de modules intégré.
Dans les premiers jours, nous écrivions nos programmes JavaScript dans des fichiers .js, et les chargions tous via des balises <script> dans nos fichiers HTML. Cela fonctionnait bien, mais cela signifiait que chaque script partageait le même environnement ; les variables déclarées dans un fichier pouvaient être accessibles dans un autre. C'était un peu le désordre.
Plusieurs solutions tierces ont été inventées, vers 2009-2010. Les plus populaires étaient RequireJS et CommonJS.
À partir du milieu des années 2010, cependant, JS a obtenu son propre système de modules natif ! Et c'est plutôt cool.
Principe de base#
Quand nous travaillons avec un système de modules JS, chaque fichier devient un "module". Un module est un fichier JavaScript qui peut contenir un ou plusieurs exports. Nous pouvons extraire le code d'un module dans un autre en utilisant l'instruction import.
Si nous n'exportons pas un morceau de données d'un module, il ne sera pas disponible dans d'autres modules. La seule façon de partager quoi que ce soit entre modules est à travers import/export.
En plus du code que nous écrivons dans notre base de code, nous pouvons également importer des modules tiers comme React.
L'avantage de ce système plutôt complexe est que tout est isolé par défaut. Nous devons faire un effort pour partager des données entre modules. Les imports/exports sont les "ponts" entre modules, et en les plaçant stratégiquement, nous pouvons rendre les programmes complexes plus faciles à raisonner.
Exports nommés#
Chaque fichier peut définir un ou plusieurs exports nommés :
export const significantNum = 5;
export function doubleNum(num) {
return num * 2;
}
Dans ce cas, notre module data.js utilise le mot-clé export pour rendre un morceau de données, significantNum, disponible à d'autres fichiers.
Nous exportons également une fonction, doubleNum. Nous pouvons exporter n'importe quel type de données JavaScript, y compris les fonctions et les classes.
Dans notre fichier principal, index.js, nous importons ces deux exports :
import { significantNum, doubleNum } from './data';
À l'intérieur des accolades, nous listons chacun des imports que nous voulons apporter, par nom. Nous n'avons pas à importer tous les exports, nous pouvons choisir juste ceux dont nous avons besoin.
La chaîne à la fin, './data', est le chemin vers le module. Nous sommes autorisés à omettre le suffixe .js, puisqu'il est implicite.
Le système de modules utilise un système de chemin relatif de style linux pour localiser les modules. Un seul point, ., fait référence au même répertoire. Deux points, .., fait référence à un répertoire parent.
Instructions d'export#
Il est conventionnel d'exporter des variables au moment où elles sont déclarées, comme ceci :
export const significantNum = 5;
Il est également possible d'exporter des variables précédemment déclarées en utilisant des accolades, comme ceci :
const significantNum = 5;
export { significantNum };
Curieusement, cette syntaxe est assez rare — j'ai seulement récemment appris qu'il était possible de faire cela ! Quand il s'agit d'exports nommés, il est beaucoup plus courant de les exporter au moment où ils sont déclarés.
Nous pouvons également exporter des fonctions au moment où elles sont déclarées, comme ceci :
// Produces a named export called `someFunction`:
export function someFunction() { /* ... */ }
Renommer les imports#
Parfois, nous rencontrerons des collisions de noms avec les imports nommés :
import { Wrapper } from './Header';
import { Wrapper } from './Footer';
// 🚫 Identifier 'Wrapper' has already been declared.
Cela arrive parce que les exports nommés n'ont pas besoin d'être globalement uniques. Il est parfaitement valide pour Header et Footer d'utiliser le même nom :
// Header.js
export function Wrapper() {
return <header>Hello</header>;
}
// Footer.js
export function Wrapper() {
return <footer>World</footer>;
}
Nous pouvons renommer les imports avec le mot-clé as :
import { Wrapper as HeaderWrapper } from './Header';
import { Wrapper as FooterWrapper } from './Footer';
// ✅ No problems
Dans la portée de ce module, HeaderWrapper fera référence à la fonction Wrapper exportée du module /Header.js. De même, FooterWrapper fera référence à l'autre fonction Wrapper exportée de /Footer.js.
Exports par défaut#
Il y a un type séparé d'export dans les modules JS : l'export par défaut.
Regardons un exemple :
const magicNumber = 100;
export default magicNumber;
Quand il s'agit d'exports par défaut, nous exportons toujours une expression :
// ✅ Correct:
const hi = 5;
export default hi;
// 🚫 Incorrect
export default const hi = 10;
Chaque module JS est limité à un seul export par défaut. Par exemple, ceci est invalide :
const hi = 5;
const bye = 10;
export default hi;
export default bye;
// 🚫 SyntaxError: Too many default exports!
Lors de l'importation d'exports par défaut, nous n'utilisons pas d'accolades :
// ✅ Correct:
import magicNumber from './data';
// 🚫 Incorrect:
import { magicNumber } from './data';
Quand nous importons un export par défaut, nous pouvons le nommer comme nous voulons ; il n'a pas besoin de correspondre :
// ✅ This works
import magicNumber from './data';
// ✅ This also works!
import helloWorld from './data';
Beaucoup de ces différences peuvent sembler arbitraires ou confuses, mais elles découlent toutes de ce fait : Chaque module peut avoir plusieurs exports nommés, mais seulement un seul export par défaut.
Par exemple, nous devons utiliser le nom correct lors de l'importation d'un export nommé parce que nous devons spécifier quel export nous voulons ! Mais parce qu'il n'y a qu'un seul export par défaut, nous pouvons le nommer comme nous voulons, il n'y a pas d'ambiguïté.
Quand utiliser quoi#
Maintenant que nous avons couvert les fondamentaux sur les exports nommés et par défaut, vous vous demandez peut-être : quand dois-je utiliser chaque type ?
En fait, c'est une question sans réponse objectivement "correcte". Tout se résume à choisir une convention qui fonctionne pour vous.
Par exemple, disons que nous avons un fichier qui contient un tas de constantes de thème (couleurs et tailles et polices et trucs). Nous pourrions le structurer comme ceci :
const THEME = {
colors: {
red: 'hsl(333deg 100% 45%)',
blue: 'hsl(220deg 80% 40%)',
},
spaces: [
'0.25rem',
'0.5rem',
'1rem',
'1.5rem',
'2rem',
'4rem',
'8rem',
],
fonts: {
default: '"Helvetica Neue", sans-serif',
mono: '"Fira Code", monospace',
},
}
export default THEME;
Ou, nous pourrions utiliser des exports nommés :
export const COLORS = {
red: 'hsl(333deg 100% 45%)',
blue: 'hsl(220deg 80% 40%)',
};
export const SPACES = [
'0.25rem',
'0.5rem',
'1rem',
'1.5rem',
'2rem',
'4rem',
'8rem',
];
export const FONTS = {
default: '"Helvetica Neue", sans-serif',
mono: '"Fira Code", monospace',
}
Nous pouvons utiliser soit un seul export par défaut "groupé", soit beaucoup d'exports nommés individuels. Ces deux options sont parfaitement valides.
Voici une convention que j'aime suivre, cependant : si un fichier a une "chose" principale évidente, j'en fais l'export par défaut. Les choses secondaires, comme les helpers et les métadonnées, peuvent être exportées en utilisant des exports nommés.
Par exemple, un composant React pourrait être configuré comme ceci :
// components/Header.js
export const HEADER_HEIGHT = '5rem';
function Header() {
return (
<header style={{ height: HEADER_HEIGHT }}>
{/* Stuff here */}
</header>
)
}
export default Header;
La "chose" principale dans ce fichier Header.js est le composant Header, et donc il utilise l'export par défaut. Tout le reste utilisera des exports nommés.
07 Méthodes d'Itération de Tableaux#
Une des choses nouvelles à propos de React est que JSX n'inclut aucun helper d'itération.
Dans la plupart des langages de template, vous avez une syntaxe personnalisée comme {{#each}} pour vous aider à itérer à travers un ensemble de données. Dans des frameworks comme Angular et Vue, vous avez des directives comme v-for pour gérer l'itération.
React ne fournit aucune abstraction ici, et donc nous nous appuierons sur les méthodes intégrées qui font partie du langage JavaScript, des méthodes comme map et filter.
Si nous pouvons devenir à l'aise avec ces méthodes principales, nous aurons beaucoup plus de facilité à faire de l'itération en React. Dans cette section, nous apprendrons quelques-unes des méthodes les plus communes et utiles.
forEach#
Nous utilisons forEach quand nous voulons effectuer une sorte d'action sur chaque élément d'un tableau.
Voici un exemple :
const pizzaToppings = [
'cheese',
'avocado',
'halibut',
'custard',
];
pizzaToppings.forEach((topping) => {
console.log(topping);
});
Dans cet exemple, nous enregistrons la valeur de chaque garniture, une à la fois. Voici ce que serait la sortie de la console :
cheese
avocado
halibut
custard
La méthode forEach accepte une fonction comme argument. Ceci est communément connu sous le nom de fonction callback.
Le terme "fonction callback" fait référence à une fonction que nous passons à une autre fonction. Dans ce cas, nous écrivons une fonction fléchée qui ressemble à ceci :
(topping) => {
console.log(topping);
}
Nous n'appelons pas cette fonction nous-mêmes ; au lieu de cela, nous la passons comme argument à la méthode forEach. Le moteur JavaScript appellera cette fonction pour nous, en fournissant l'argument topping pendant qu'il itère sur le tableau.
Accéder à l'index#
Le callback que nous passons à forEach prend un deuxième paramètre optionnel :
const pizzaToppings = [
'cheese',
'avocado',
'halibut',
'custard',
];
pizzaToppings.forEach((topping, index) => {
console.log(index, topping);
});
L'index est la position dans le tableau de l'élément actuel, en commençant par 0. Ce code enregistrerait :
0 cheese
1 avocado
2 halibut
3 custard
Un format commun#
JavaScript nous donne plusieurs outils pour itérer sur les éléments d'un tableau. Nous aurions pu utiliser une boucle for, et c'est sans doute beaucoup plus simple. Il n'y a pas de fonctions callback compliquées ! Alors pourquoi devrions-nous apprendre .forEach ?
Voici le plus grand avantage : forEach fait partie d'une famille de méthodes d'itération de tableaux. Prise dans son ensemble, cette famille nous permet de faire toutes sortes de choses incroyables, comme trouver un élément particulier dans le tableau, filtrer une liste, et bien plus.
Toutes les méthodes de cette famille suivent la même structure de base. Par exemple, elles supportent toutes le paramètre optionnel index que nous venons de voir !
Regardons un autre membre de cette famille : la méthode filter.
filter#
Les choses commencent à devenir vraiment intéressantes avec filter.
Voici un exemple rapide :
const students = [
{ name: 'Aisha', grade: 89 },
{ name: 'Bruno', grade: 55 },
{ name: 'Carlos', grade: 68 },
{ name: 'Dacian', grade: 71 },
{ name: 'Esther', grade: 40 },
];
const studentsWhoPassed = students.filter(student => {
return student.grade >= 60;
});
console.log(studentsWhoPassed);
/*
[
{ name: 'Aisha', grade: 89 },
{ name: 'Carlos', grade: 68 },
{ name: 'Dacian', grade: 71 },
]
*/
À bien des égards, filter est très similaire à forEach. Il prend une fonction callback, et cette fonction callback sera appelée une fois par élément dans le tableau.
Contrairement à forEach, cependant, filter produit une valeur. Spécifiquement, il produit un nouveau tableau qui contient un sous-ensemble d'éléments du tableau original.
Typiquement, notre fonction callback devrait retourner une valeur booléenne, soit true ou false. La méthode filter appelle cette fonction une fois pour chaque élément du tableau. Si le callback retourne true, cet élément est inclus dans le nouveau tableau. Sinon, il est exclu.
Important à noter : La méthode filter ne modifie pas le tableau original. Ceci est vrai pour toutes les méthodes de tableau discutées dans cette leçon.
Voici un autre exemple :
const nums = [5, 12, 15, 31, 40];
const evenNums = nums.filter(num => {
return num % 2 === 0;
});
console.log(nums); // Hasn't changed: [5, 12, 15, 31, 40]
console.log(evenNums); // [12, 40]
map#
Finalement, nous avons la méthode map. C'est la méthode de tableau la plus couramment utilisée, lors du travail avec React.
Commençons par un exemple :
const people = [
{ name: 'Aisha', grade: 89 },
{ name: 'Bruno', grade: 55 },
{ name: 'Carlos', grade: 68 },
{ name: 'Dacian', grade: 71 },
{ name: 'Esther', grade: 40 },
];
const screamedNames = people.map(person => {
return person.name.toUpperCase();
});
console.log(screamedNames);
/*
['AISHA', 'BRUNO', 'CARLOS', 'DACIAN', 'ESTHER']
*/
À bien des égards, map ressemble beaucoup à forEach. Nous lui donnons une fonction callback, et il itère sur le tableau, appelant la fonction une fois pour chaque élément du tableau.
Voici la grande différence, cependant : map produit un tout nouveau tableau, plein de valeurs transformées.
La fonction forEach retournera toujours undefined :
const nums = [1, 2, 3];
const result = nums.forEach(num => num + 1);
console.log(result); // undefined
En contraste, map va "collecter" toutes les valeurs que nous retournons de notre callback, et les mettre dans un nouveau tableau :
const nums = [1, 2, 3];
const result = nums.map(num => num + 1);
console.log(result); // [2, 3, 4]
Comme filter, map ne mute pas le tableau original ; il produit un tout nouveau tableau.
Aussi, le nouveau tableau aura toujours exactement la même longueur que le tableau original. Nous ne pouvons pas "ignorer" certains éléments en retournant false ou en ne retournant rien du tout :
const people = [
{ id: 'a', name: 'Aisha' },
{ id: 'b' },
{ id: 'c' },
{ id: 'd', name: 'Dacian' },
{ id: 'e' },
];
const screamedNames = people.map(person => {
if (person.name) {
return person.name.toUpperCase();
}
});
console.log(screamedNames);
/*
['AISHA', undefined, undefined, 'DACIAN', undefined]
*/
Voici une façon utile de penser à map : c'est exactement comme forEach, sauf qu'il "sauvegarde" tout ce que nous retournons du callback dans un nouveau tableau.
Accéder à l'index avec map#
Comme les autres méthodes que nous avons vues, nous pouvons passer un deuxième paramètre pour accéder à l'index de l'élément actuel :
const people = [
{ name: 'Aisha', grade: 89 },
{ name: 'Bruno', grade: 55 },
{ name: 'Carlos', grade: 68 },
{ name: 'Dacian', grade: 71 },
{ name: 'Esther', grade: 40 },
];
const numberedNames = people.map((person, index) => {
return `${index}-${person.name}`;
});
console.log(numberedNames);
/*
['0-Aisha', '1-Bruno', '2-Carlos', '3-Dacian', '4-Esther']
*/
Méthodes supplémentaires#
Nous avons vu 3 des méthodes les plus communes ici, mais il y en a en fait plusieurs autres qui suivent ce même format, incluant find (sélectionner un seul élément du tableau), every (vérifier si tous les éléments d'un tableau remplissent une condition), et reduce (combiner tous les éléments du tableau en une seule valeur finale).
Vous pouvez en apprendre plus sur ces méthodes sur MDN.
08 Truthy et Falsy#
Considérons l'instruction JavaScript suivante :
const user = {
name: 'J. Script',
};
if (user.name) {
console.log('This user has a name!');
}
Nous avons un objet user, et nous voulons exécuter un console.log selon une condition. La condition est l'expression JavaScript user.name.
Fait intéressant, user.name n'est pas une valeur booléenne. Le nom de l'utilisateur n'est ni true ni false. Dans beaucoup de langages de programmation, ce serait une opération illégale. Comment le langage devrait-il savoir si une chaîne aléatoire est suffisante ou non ??
En JavaScript, chaque valeur est soit "truthy" ou "falsy". Une valeur truthy est une qui compte comme true quand il s'agit de conditions.
La plupart des valeurs en JavaScript sont truthy. Il est plus rapide de lister toutes les valeurs falsy :
- false
- null
- undefined
- '' (chaîne vide)
- 0 (et valeurs liées, comme 0.0 et -0)
- NaN
Dans le code ci-dessus, user.name est une valeur truthy, parce que c'est une chaîne avec au moins 1 caractère. Chaque chaîne autre que '' est truthy.
Assez surprenant, [] (tableau vide) et {} (objet vide) sont des valeurs truthy, pas falsy. Cela signifie que chaque objet/tableau est truthy.
Conversion en booléen#
Parfois, il est bénéfique de convertir une valeur truthy en true, ou une valeur falsy en false.
La façon la plus déclarative de faire cela est d'utiliser le constructeur Boolean() :
Boolean(4); // true
Boolean(0); // false
Boolean([]); // true
Boolean(''); // false
Il y a une autre façon plus commune de convertir en booléen. Et, si vous n'êtes pas familier avec elle, elle peut sembler un peu bizarre :
!!4; // true
!!0; // false
!![]; // true
!!''; // false
!! n'est pas vraiment un opérateur JavaScript ; nous répétons l'opérateur NOT (!) deux fois.
Nous pouvons utiliser ! pour inverser une valeur booléenne :
!true; // false
!false; // true
Si nous utilisons le ! avec une valeur non-booléenne, il inversera une valeur truthy en false, ou une valeur falsy en true :
!4; // false, since 4 is truthy
!0; // true, since 0 is falsy
Nous pouvons empiler plusieurs opérateurs ! pour l'inverser d'avant en arrière :
!4; // false
!!4; // true
!!!4; // false
!!!!4; // true
Pour décomposer ce qui se passe ici : Chaque ! est évalué, un à la fois. C'est comme s'il y avait des parenthèses autour de chaque ensemble, comme ceci :
!(!(!4))
^^ !4 resolves to `false`
!(!false)
^^^^^^ !false resolves to `true`
!true
^^^^^ !true resolves to `false`
Je ne suis pas sûr pourquoi !! est devenu la façon standard de facto de convertir en booléen ; le constructeur Boolean() me semble beaucoup plus intuitif !
09 Opérateurs Logiques#
Si vous travaillez avec JavaScript depuis un moment, vous êtes probablement déjà familier avec l'opérateur AND (&&) et l'opérateur OR (||) :
const isLoggedIn = true;
const userRole = 'administrator';
if (isLoggedIn && userRole === 'administrator') {
// This code only runs if BOTH conditions above are truthy
}
Il y a quelque chose que beaucoup de développeurs JavaScript ne réalisent pas à propos de ces opérateurs, cependant : ils agissent aussi comme opérateurs de flux de contrôle.
Testons vos connaissances avec une question à choix multiples rapide : Quelle est la valeur de result ?
const myAge = 35;
const result = myAge < 50 && myAge;
- true
- false
- 35
- Aucune des réponses ci-dessus
Il est totalement raisonnable de s'attendre à ce que la réponse soit true :
- myAge < 50 se résout à true
- myAge se résout à 35, qui est une valeur truthy
- Si les deux côtés sont truthy, le tout devrait s'évaluer à true, non ?
Pas du tout ! L'opérateur && n'est pas conçu pour produire une valeur booléenne. Il est conçu pour se résoudre à l'une des expressions de chaque côté de l'opérateur.
Vous pouvez penser à && comme une porte. Nous sommes autorisés à passer à travers la porte si le côté gauche est truthy.
Cela devient plus clair quand nous enchaînons plusieurs && ensemble :
const numOfChildren = 4;
const parkingHasBeenValidated = true;
const signatureOnWaiver = '';
const admissionTicket =
numOfChildren &&
parkingHasBeenValidated &&
signatureOnWaiver &&
generateTicket();
La première expression dans cette chaîne est numOfChildren, qui est 4. C'est une valeur truthy, et donc la première porte && est déverrouillée.
La deuxième expression est parkingHasBeenValidated, qui est true. Nous passons à travers la deuxième porte.
La troisième expression, signatureOnWaiver, est une chaîne vide (''), qui est falsy. Cela signifie que la troisième porte && reste verrouillée. Nous ne pouvons pas aller plus loin. L'expression actuelle, '', est ce qui est résolu.
En conséquence, admissionTicket serait assigné ''. Pas false.
Supposons que signatureOnWaiver ne soit pas une chaîne vide :
const numOfChildren = 4;
const parkingHasBeenValidated = true;
const signatureOnWaiver = 'Becky Chambers';
const admissionTicket =
numOfChildren &&
parkingHasBeenValidated &&
signatureOnWaiver &&
generateTicket();
Maintenant, quand nous arrivons à la troisième expression, nous obtenons une valeur truthy (la chaîne "Becky Chambers"). La porte finale est déverrouillée.
Parce que toutes les portes sont déverrouillées, l'expression finale sera transmise. admissionTicket sera égal à ce que la fonction generateTicket retourne.
Voici comment nous configurerions exactement le même code, mais en utilisant if/else au lieu d'opérateurs logiques :
let admissionTicket;
if (!numOfChildren) {
admissionTicket = numOfChildren;
} else if (!parkingHasBeenValidated) {
admissionTicket = parkingHasBeenValidated;
} else if (!signatureOnWaiver) {
admissionTicket = signatureOnWaiver;
} else {
admissionTicket = generateTicket();
}
admissionTicket sera assigné à la première expression falsy. Si nous passons à travers toutes les portes, admissionTicket sera assigné à l'expression finale, qu'elle soit truthy ou falsy.
Il y a une autre conclusion intéressante ici : La fonction generateTicket n'est appelée que si nous passons à travers toutes les portes. Dans le premier exemple, quand signatureOnWaiver était falsy, la fonction generateTicket n'a jamais été appelée.
C'est à cause du court-circuitage. Si une des expressions avant un opérateur && s'évalue à une valeur falsy, le reste des expressions est ignoré. Elles ne seront pas exécutées du tout.
Voici un exemple moins complexe :
false && console.log('I will never run');
Parce que false est une valeur falsy, la porte && reste verrouillée, et le console.log ne se déclenche jamais. C'est la même chose que si le console.log était dans une instruction if :
if (false) {
console.log('I will never run');
}
L'opérateur OR#
L'opérateur || est exactement comme l'opérateur &&, avec une différence clé : || ne s'arrête pas et ne fournit pas la première valeur falsy. Il s'arrête et fournit la première valeur truthy.
Par exemple :
const userImageSrc = null;
const teamImageSrc = null;
const defaultImageSrc = '/images/cat.jpg';
const src = userImageSrc || teamImageSrc || defaultImageSrc;
Nous vérifions la première expression, userImageSrc, qui est null. Parce qu'elle est falsy, la porte || est déverrouillée, et nous procédons.
La deuxième expression, teamImageSrc, est aussi null, et donc la deuxième porte || se déverrouille.
Comme avec &&, quand nous arrivons au dernier élément, le dernier élément est fourni indépendamment du fait qu'il soit truthy ou falsy. Et donc src sera assigné à defaultImageSrc.
Cependant, dans ce cas :
const userImageSrc = '/images/my-avatar.png';
const teamImageSrc = null;
const defaultImageSrc = '/images/cat.jpg';
const src = userImageSrc || teamImageSrc || defaultImageSrc;
Le premier élément, userImageSrc, est truthy ! Parce qu'il est truthy, la première porte || reste verrouillée, et src est assigné à userImageSrc.
Voici comment nous représenterions ce code en utilisant if/else :
let src;
if (userImageSrc) {
src = userImageSrc;
} else if (teamImageSrc) {
src = teamImageSrc;
} else {
src = defaultImageSrc;
}
10 Assignation vs. Mutation#
en cours...
11 Rest / Spread#
en cours...
12 Intervals et Timeouts#
en cours...
13 Événements Globaux#
en cours...
14 Async / Await#
en cours...
15 Méthodes HTTP#
en cours...
Fetch#
en cours...
16 Codes de Statut HTTP#
en cours...