Aller au contenu principal

Recapt Mars 2022

· 6 minutes de lecture
Damien Buchet

1ère édition de mon Recapt ! Les nouveautés et nouvelles qu'il ne fallait pas manquer sur l'écosystème React / Javascript, de Mars 2022 (et d'un peu avant Mars j'avoue, mais fallait pas passer à côté 🤗)

React 18

React 18 est sorti ce mardi ! En résumé, il apporte notamment :

  • L'automatic batching pour les setState après un appel asynchrone
import React, { useState } from 'react';

const App = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');

const onClick = async () => {
setFirstName('Damien');
setLastName('Buchet');

// Do an asynchronous "pause" of 1.5 seconds
await new Promise(resolve => setTimeout(() => resolve(), 1500));

setFirstName('Damien-Updated');
setLastName('Buchet-Updated');
setEmail('[email protected]');
};

console.log("I'm rendered!", firstName, lastName, email);

return <button onClick={onClick}>Click me</button>;
};

export default App;

Dans cet exemple, seuls les 2 premiers setState sont regroupés et ne produisent qu'un seul re-render. Après avoir "attendu" 1.5s (pour simuler un chargement asynchrone par exemple) les 3 setState provoquent chacun un re-render.

Ca ne sera plus le cas avec React 18 qui regroupera bien ces setState pour ne faire qu'un seul re-render

  • Une nouvelle API startTransition pour définir des updates "non-urgente"
import { startTransition } from 'react';

// Urgent : Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results - Can be delayed
setSearchQuery(input);
});

Concrètement comment ca va marcher ? Ca va permettre de définir des updates "urgentes" (frappe clavier, un clic, actions utilisateur,...) et "non-urgente" (affichage de résultats, transition de vues,...)

Par exemple lors d'une frappe clavier sur un champ de recherche, qui va proposer des résultats d'autocomplétion. Les actions urgentes sont les frappes claviers (pour que l'utilisateur voit bien ce qu'il tape), par contre l'affichage des résultats peut être considéré "non-urgent".

Par exemple :

import React, { useState, useCallback } from 'react';

const STATES = ['Alabama','Alaska','American Samoa','Arizona','Arkansas','California','Colorado','Connecticut','Delaware','District of Columbia','Federated States of Micronesia','Florida','Georgia','Guam','Hawaii','Idaho','Illinois','Indiana','Iowa','Kansas','Kentucky','Louisiana','Maine','Marshall Islands','Maryland','Massachusetts','Michigan','Minnesota','Mississippi','Missouri','Montana','Nebraska','Nevada','New Hampshire','New Jersey','New Mexico','New York','North Carolina','North Dakota','Northern Mariana Islands','Ohio','Oklahoma','Oregon','Palau','Pennsylvania','Puerto Rico','Rhode Island','South Carolina','South Dakota','Tennessee','Texas','Utah','Vermont','Virgin Island','Virginia','Washington','West Virginia','Wisconsin','Wyoming'];

const App = () => {
const [search, setSearch] = useState();
const [filteredResults, setFilteredResults] = useState(STATES);

const _onChangeTyped = useCallback(({target:{value}}) => {
setSearch(value);
setFilteredResults(STATES.filter(state => state.toLowerCase().includes(value.toLowerCase())))
}, []);

return (<>
<input type="text" value={search} onChange={_onChangeTyped} />
<ul>
{filteredResults.map(state => (
<li key={state}>{state}</li>
))}
</ul>
</>)
};

export default App;

A chaque frappe clavier, nous allons filtrer le tableau de résultat, ce qui n'a pas grand intérêt si l'utilisateur est toujours en train de taper au clavier. Jusqu'alors, nous avions des fonctionnalités comme debounce ou throttle qui nous permettait de ne pas effectuer une action plus de X fois par Y millisecondes, ou d'invoquer uniquement une fonction après X millisecondes.

Avec startTransition on va pouvoir avoir ce comportement "nativement". React peut, en cas de re-render "bloquant", différer le rendu que provoquerait un setState si celui-ci est défini dans un startTransition

A noter aussi que React fournit un nouvel hook useTransition

import { useTransition } from 'react';

const [isPending, startTransition] = useTransition();

Qui permet de savoir si l'état de la transition est "en attente" ou non. Ce qui vous permet d'afficher un petit spinner / loader / ou ce que vous voulez !

  • Comment ? Tu ne parles pas de Suspense ?

Alors non... Pas encore 🙂 Enfin un peu. Suspense ca permet "en gros" de faire des call API en même temps que le render du composant (et non pas après le 1er render avec un useEffect). Pourquoi j'en parle pas maintenant ? Car uniquement Relay implémente ce pattern, ou alors des packages tierces comme react-suspense-fetch qui sont clairement encore au stade experimental. Donc c'est encore trop tôt, mais à surveiller 🙂

Safari 15.4

L'update de Safari apporte (enfin) un énorme lot de fonctionnalités, à découvrir ici. Et surtout (enfin) des compatibilités CSS et JS (qui étaient déjà présent depuis un moment dans Chromium)

Je note surtout l'ajout de Array.at() qui permet (enfin) de manipuler les tableaux en utilisant .at !

const array = ["🍎 apple", "🍊 orange", "🥭 mango", "🍌 banana"]
array.at(0) // "🍎 apple"
array.at(-1) // "🍌 banana"
array.at(-2) // "🥭 mango"

Ainsi que le support de structuredClone qui permet de faire des copies profondes (deep clone) d'un objet / tableau

const myObject = {
a: "foo",
b: {
value: "bar"
}
}
const clonedObject = structuredClone(myObject);
console.log(clonedObject === myObject) // false
console.log(clonedObject.b === myObject.b) // false

const myArray = ["foo", { value: "bar" }];
const clonedArray = structuredClone(myArray);
console.log(clonedArray === myArray) // false
console.log(clonedArray.at(-1) === myArray.at(-1)) // false
Rappel

Un spread operator ne fait qu'une copie de surface (shallow copy). Si on reprend les exemples suivant avec du spread operator, on aura :

const myObject = {
a: "foo",
b: {
value: "bar"
}
}
const spreadObject = { ...myObject };
console.log(spreadObject === myObject) // false
console.log(spreadObject.b === myObject.b) // true ⚠️

const myArray = ["foo", { value: "bar" }];
const spreadArray = [...myArray];
console.log(spreadArray === myArray) // false
console.log(spreadArray.at(-1) === myArray.at(-1)) // true ⚠️

NextJS

2 news sur le framework NextJS

  • NextJS a complètement mis à jour son "parcours d'apprentissage" du framework, et c'est gratuit ! 👉 https://nextjs.org/learn/
  • L'ISR On-Demand est en béta, et c'est clairement le futur ! Vous pouvez lire mon article sur CSR, SSR, SSG, ISR pour savoir ce qu'est l'ISR On Demand

Proposition de Types en JS

L'équipe core de Typescript a fait une proposition pour apporter du typage dans Javascript (sans passer par TS donc). Cela permettrait pendant le développement d'avoir un "Type Checker" qui nous informerait de potentielles erreurs dans notre code.

Par contre ce ne serait pas "natif" au javascript, ces types étant supprimés et transformés en commentaires lors de la compilation.

Vous pouvez en découvrir plus par ici

A surveiller

  • Vitest - Un framework de test unitaire powered by Vite
  • Turborepo - Système de build haute performance pour JS et TS

Outils

  • TinyWow - Une toolbox pour faire... tout et n'importe quoi :)
  • TinyPng - Compressez vos PNG / JPEG sans aucune perte
  • Remove.bg - Supprimez le background d'une image
  • Photopea - Photoshop-lite (mais performant) en ligne
  • Pixlr - Photoshop-lite (mais performant) en ligne (un 2ème)
  • FlatIcon - Génerateur d'icones "Flat Design"

WTF JS?

Que va afficher ce console.log ?

console.log(('b' + 'a' + + 'a' + 'a').toLowerCase())
Afficher la solution et les explications !

😱 'banana' 😱

Alors pourquoi ca ? Vous avez sûrement remarqué le + + 'a'. Si on réécrit l'opération avec des parenthèses, on aurait :

'b' + 'a' + (+'a') + 'a'

Et ce +'a' vient transtyper 'a' en number. Exactement comme si vous faisiez un Number('a'). Et Number('a') ca donne... NaN.

Nous avons donc l'opération :

'b' + 'a' + NaN + 'a'

Et comme nous sommes dans un String (puisque la 1ère variable à concaténer est un String, tout le reste de l'expression sera traité en tant que String), NaN va être à son tour transtypé en String. Donc String(NaN) qui donne... 'NaN'

Nous avons donc :

'b' + 'a' + 'NaN' + 'a'
=> 'baNaNa'
=> .toLowerCase()
=> 'banana'

🍌

Ne manquez pas un article !

Envie de recevoir par email mes derniers articles et Recapt ?

Essayez de taper register <votre adresse email> dans la console 🎉