5 - ES6
EcmaScript 6
Dans cette section, on va reformater notre code pour qu’il réponde aux normes les plus récentes d’EcmaScript 61.
Arrow functions
Pour ça, retournez dans le composant RepositoriesList
, et remplacez toutes les fonctions par la syntaxe (...) => {...}
. Vous devriez avoir quelque chose comme ça
import React, { Component } from 'react';
import axios, { AxiosResponse } from 'axios';
import Repository from './Repository';
export interface GitHubRepo {
id: number;
name: string;
html_url: string;
description: string;
owner: {
login: string;
avatar_url: string;
html_url: string;
};
created_at: Date;
pushed_at: Date;
forks_count: number;
open_issues_count: number;
stargazers_count: number;
watchers: number;
}
interface Props {
language: string;
}
interface State {
repositories: GitHubRepo[];
}
class RepositoriesList extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
repositories: []
};
}
public componentDidMount = () => {
const url: string = RepositoriesList.formatUrl(this.props.language);
axios.get(url).then((res: AxiosResponse) => {
this.setState({ repositories: res.data.items });
});
}
public render = (): JSX.Element[] | string => {
const rep: JSX.Element[] = [];
for (const r of this.state.repositories) {
rep.push(<Repository key={r.id} {...r} />);
}
return rep.length > 0 ? rep : 'No results found...';
}
private static formatUrl = (language: string): string =>
`https://api.github.com/search/repositories?q=language:${language}&sort=stars&order=desc`
}
export default RepositoriesList;
Il y a rarement une vraie nécessité à utiliser cette syntaxe, mais elle est fortement recommandée par tous les grands acteurs d’EcmaScript.
Ensuite, on va modifier la méthode componentDidMount
car dans cette méthode, on récupère un objet Promise
depuis la méthode axios.get
.
Async Await
Pour ceux qui ne sont pas familier avec le concept des Promise, je vais pas l’expliquer ici, mais je vous laisse lire cet article qui les explique de manière assez synthétique.
Dans notre code, on a géré la Promise de la manière classique (avec la fonction then
) mais ce genre de code peut très vite devenir très compliqué à maintenir, car si l’on commence par exemple à lancer des requêtes qui attende le résultat pour lancer une autre requête et ainsi de suite, on va se retrouver avec des callbacks dans des callbacks, dans des callbacks, dans des callbacks… enfin vous avez compris.
Heureusement, depuis ES6, il existe une manière extrêmement simple de gérer ce genre de problème. Prenons l’exemple que je viens de citer juste au dessus
const callApi = (url) {
axios.get(url).then(response => {
axios.get(response.data.url).then(res => {
axios.get(res.data[0].url).then(r => {
...
})
})
})
}
Une fonction comme celle-ci-, j’en ai déjà vu plein, et c’est pas jojo à relire ni à maintenir. Encore là ça va parce qu’il n’y a que des requêtes, mais le plus souvent il y a pas mal de traitement entre chaque requêtes, et ça devient très vite illisible.
Mais ES6 vient à la rescousse ! Maintenant avec les mots clés async/await
c’est devenu beauuuuuucoup mieux. La même fonction que la précédente, mais avec async
et await
const callApi = async (url) => {
const response = await axios.get(url);
const res = await axios.get(response.data.url);
const r = await axios.get(r);
...
}
C’est quand même plus lisible ! Du coup pour expliquer un peu, async
sert à déclarer une fonction qui utilisera des Promise et donc dans laquelle on pourra utiliser await
. Ce dernier sert quant à lui à indiquer que l’on va attendre la réponse de la fonction qui se trouve après await
et retourner le résultat de la Promise plutôt que la Promise elle même.
Maintenant qu’on a vu le principe des async/await
, on va pouvoir modifier la méthode componentDidMount
.
public componentDidMount = async () => {
const url: string = RepositoriesList.formatUrl(this.props.language);
const res: AxiosResponse = await axios.get(url);
this.setState({ repositories: res.data.items });
}
Dans notre cas, la méthode n’était déjà pas très compliqué, donc ce n’était pas forcément nécessaire de l’écrire de cette façon, mais c’est une bonne pratique d’utiliser cette syntaxe afin de pouvoir l’employer et la comprendre facilement si on doit s’y frotter un jour.
Ensuite on va voir le destructuring !
Destructuring
Le destructuring, c’est des trucs les plus cons mais les plus pratiques que j’ai jamais vu.
Imaginons que j’ai un objet qui ressemble à ça
{
foo: 'bar',
toto: 'titi',
polo: 'lulu'
}
Cet objet est dans le state de mon composant. Et je suis en train d’écrire une fonction où je vais devoir utiliser ces trois champs. Du coup trois solutions s’offrent à moi.
-
J’utilise les champs tels quel. C’est à dire qu’à chaque fois que j’ai besoin d’un champs, je vais l’appeler par
this.state.toto
par exemple. Si j’ai besoin plein de fois d’appeler ces champs, ça fait une syntaxe vachement lourde. -
Je copie ces champs un par un dans des variables
function() { const foo = this.state.foo; const toto = this.state.toto; const polo = this.state.polo; ... }
Ça va permettre d’alléger le code qui suit ces déclarations, mais ça nous fait ajouter une ligne par champs qu’on va utiliser. C’est relou.
-
J’utilise le destructuring !
function() { const { foo, toto, polo } = this.state; ... }
En fait, en pratique ce code est strictement identique en terme d’exécution que le solution précédente, mais la syntaxe est quand même beaucoup plus légère !
Utilisons ce nouveau principe là où on peut. Dans la méthode componentDidMount
public componentDidMount = async () => {
const url: string = RepositoriesList.formatUrl(this.props.language);
const { data }: AxiosResponse = await axios.get(url);
this.setState({ repositories: data.items });
}
Un autre concept important d’ES6 que l’on a déjà utilisé est le spread.
Spread
L’opérateur spread est encore une fonctionnalité super pratique ajouté par ES6. Imaginons, que j’ai une variable qui est un tableau, et je vais instancier copier l’intégralité de ce tableau dans un autre. Ce n’est pas très compliqué à coder, certes, mais ça prendrait quelques lignes quand même. Alors que grâce au spread, on peut le faire en une ligne.
const array = ['foo', 'bar', 'toto', 'tutu'];
const a = [...array];
console.log(a) // ['foo', 'bar', 'toto', 'tutu']
Et voilà, le contenu de notre tableau est copié ! Et avec ça on peut même ajouter facilement une ou plusieurs entrées.
const array = ['foo', 'bar', 'toto', 'tutu'];
const a = [...array, 'polo', 'lulu'];
console.log(a) // ['foo', 'bar', 'toto', 'tutu', 'polo', 'lulu']
Et le plus beaux, c’est que ça marche aussi pour les objets !
const object = { foo: 'bar', toto: 1 };
const inheritedObject = {...object, polo: 'lulu'};
console.log(inheritedObject); // { foo: 'bar', toto: 1, polo: 'lulu' }
Si ça vous dit quelque chose, c’est normal. On a déjà utilisé pour passer ses props au composant Repository
.
Pour finir ce chapitre, on va juste revoir la méthode render
.
Améliorer le render
Ça n’a rien à voir avec ES6, mais notre méthode render est assez caca pour du React. Ça n’a rien de grave, mais une des bonnes pratiques de Javascript est d’utiliser le moins possible de boucles for, tout simplement parce que la class Array
de Javascript possède déjà plein de méthode super pratique au même titre que les List
de Java. Ceci étant dit, on va reformater la méthode render
public render = (): JSX.Element[] | string => {
const { repositories } = this.state;
if (repositories.length) {
return repositories.map(r => <Repository key={r.id} {...r} />);
} else {
return 'No results found';
}
}
Enfin, on va juste rajouter un petit quelque chose qui nous permettra d’indiquer à l’utilisateur que la page est en cours de chargement, car pour l’instant, la page nous indique seulement des résultats, ou No results found...
Je vous fournis le code, et je vous laisse constater par vous même les changements effectués :wink:
import React, { Component } from 'react';
import axios, { AxiosResponse } from 'axios';
import Repository from './Repository';
export interface GitHubRepo {
id: number;
name: string;
html_url: string;
description: string;
owner: {
login: string;
avatar_url: string;
html_url: string;
};
created_at: Date;
pushed_at: Date;
forks_count: number;
open_issues_count: number;
stargazers_count: number;
watchers: number;
}
interface Props {
language: string;
}
interface State {
repositories: GitHubRepo[];
loading: boolean;
}
class RepositoriesList extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
repositories: [],
loading: true
};
}
public componentDidMount = async () => {
const url: string = RepositoriesList.formatUrl(this.props.language);
axios
.get(url)
.then(({ data }) => {
this.setState({ repositories: data.items, loading: false });
})
.catch(err => {
console.error(err);
this.setState({ loading: false });
});
}
public render = (): JSX.Element | JSX.Element[] | string => {
const { repositories, loading } = this.state;
if (loading) {
return 'Loading...';
}
if (repositories.length) {
return repositories.map(r => <Repository key={r.id} {...r} />);
} else {
return <span style=>No results found</span>;
}
}
private static formatUrl = (language: string): string =>
`https://api.github.com/search/repositories?q=language:${language}qoijd&sort=stars&order=desc`
}
export default RepositoriesList;
Voilà qui conclut ce chapitre ! Dans le suivant on va faire un peu de gestion de formulaire, et d’évènement.
-
Un ensemble de normes de syntaxe que JavaScript et TypeScript implémente ↩