• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 09/09/2020

Initiez-vous aux cycles de vie des components

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

En React Native, et plus globalement en React, un component est créé lorsqu'on l'affiche, puis il est éventuellement modifié et enfin, il est supprimé lorsqu'il n'est plus affiché à l'écran. Chacune de ces étapes correspond à un cycle de vie.

Dans ce chapitre, je vais vous apprendre à réaliser des actions lorsque vos components changent de cycle de vie. Enfin, apprendre est un grand mot. Sans trop le savoir, on réalise ces types d'action depuis le début de ce cours, vous allez voir. ;)

On va également en profiter pour créer la vue détail du film (FilmDetail) que l'on a juste démarré dans le chapitre précédent.

Récupérez le détail d'un film

Dans le chapitre précédent, on a fait passer l'identifiant du film cliqué via la navigation. Je vous ai demandé de faire cela, car cet identifiant va nous être utile pour récupérer le détail d'un film via l'API TMDB.

La récupération du détail d'un film passe par une nouvelle URL de l'API TMDB. Commençons donc par rajouter une fonction dans notre fichier de gestion d'API : TMDBApi.js.

// API/TMDBApi.js
// Récupération du détail d'un film
export function getFilmDetailFromApi (id) {
return fetch('https://api.themoviedb.org/3/movie/' + id + '?api_key=' + API_TOKEN + '&language=fr')
.then((response) => response.json())
.catch((error) => console.error(error));
}

La fonction est quasi équivalente à celle de la recherche. La différence se situe dans le fait que l'on passe un identifiant en paramètre et que l'on appelle une nouvelle URL.

Avant d'appeler cette fonction, on va afficher un chargement à l'ouverture de notre vue FilmDetail, histoire de faire patienter l'utilisateur pendant que l'on récupère le détail du film depuis l'API. Vous connaissez la démarche. On va également enlever le texte "Détail du film idFilm"

// Components/FilmDetail.js
import React from 'react'
import { StyleSheet, View, ActivityIndicator } from 'react-native'
class FilmDetail extends React.Component {
constructor(props) {
super(props)
this.state = {
film: undefined, // Pour l'instant on n'a pas les infos du film, on initialise donc le film à undefined.
isLoading: true // A l'ouverture de la vue, on affiche le chargement, le temps de récupérer le détail du film
}
}
_displayLoading() {
if (this.state.isLoading) {
// Si isLoading vaut true, on affiche le chargement à l'écran
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
}
})
export default FilmDetail

La différence, par rapport au chargement sur la vue Search, est qu'ici, le chargement est affiché par défaut  isLoading: true et que l'on veut l'afficher en plein écran (pas de marge  top: 100 ). 

Lorsque vous affichez la vue FilmDetail, vous avez maintenant ce résultat :

Chargement par défaut sur la vue FilmDetail
Chargement par défaut sur la vue FilmDetail

C'est ce que l'on voulait. À présent, on va appeler la fonction d'API pour récupérer le détail d'un film dans notre component FilmDetail. Seulement, il y a une spécificité ici. Je veux que vous appeliez cette fonction seulement lorsque votre component est monté, c'est-à-dire lorsque votre component FilmDetail est créé et affiché à l'écran. Ah ah, là, je vous ai bloqué. :D C'est normal, on n'a pas encore vu ce point. C'est ici qu'interviennent les cycles de vie des components.

Component Lifecycle

Un cycle de vie d'un component correspond à un changement de son état. Il existe 3 cycles de vie : mounting,updating et unmounting.

React décompose chaque cycle de vie en plusieurs étapes. J'ai réalisé pour vous un petit schéma assez explicite avec le détail de ces étapes. Je me suis beaucoup appuyé sur une réalisation de codevoila.com, mais en simplifiant un tout petit peu :

Cycles de vie des components en React
Cycles de vie des components en React

À première vue, cela fait peur, mais vous allez voir que c'est très simple. :) Chaque étape correspond en fait à une méthode que l'on peut surcharger (réécrire) en React. 

Prenez l'exemple du cycle de vie mounting. Lorsque vous souhaitez ajouter un component à l'écran, voici ce qu'il se passe : 

  1. React appelle le constructeur de votre component  constructor . Vous pouvez très bien réécrire cette méthode et ajouter des opérations. React les exécutera à l'initialisation de votre component. Je vois déjà venir votre remarque, alors "oui, vous l'avez déjà fait". Sans trop le savoir, depuis le début de ce cours, on ajoute des opérations pendant le cycle de vie mounting.

  2. React appelle ensuite la méthode statique  getDerivedStateFromProps . Si vous regardez bien le schéma, cette méthode est appelée juste avant le  render  de votre component. Il peut donc être utile de réécrire cette méthode si vous souhaitez mettre à jour votre state en fonction des props reçues, et ceci juste avant que votre component soit rendu.

  3. La méthode  render, vous la connaissez déjà. ;)

  4. La méthode  componentDidMount  est appelée quand votre component a fini d'être rendu. Si vous souhaitez effectuer des opérations juste après que votre component est rendu, c'est ici que vous pourrez les faire.

Revenons à notre application. Je vous ai demandé tout à l'heure d'effectuer l'appel API pour récupérer le détail d'un film lorsque votre component FilmDetail a fini d'être rendu. Avec tout ce que je viens de vous dire, vous devriez avoir une petite idée de la méthode à surcharger. :D C'est bien sûr la méthode componentDidMount  .

Surchargez l'étape d'un cycle de vie

Vous connaissez déjà la démarche. C'est la même que pour le  constructor . On va donc surcharger la méthode  componentDidMount . Profitez-en pour ajouter des logs un peu partout et vous rendre compte par vous-même des ordres d'appel des méthodes : 

// Components/FilmDetail.js
class FilmDetail extends React.Component {
...
componentDidMount() {
console.log("Component FilmDetail monté")
}
render() {
console.log("Component FilmDetail rendu")
return (
<View style={styles.main_container}>
{this._displayLoading()}
</View>
)
}
}

Lorsque vous affichez un film, au niveau des logs, cela doit donner ça :

09:02:20: Display film with id 11
09:02:20: Component FilmDetail rendu
09:02:20: Component FilmDetail monté

Ouf... mon schéma est bon. :p Une fois dans  componentDidMount  , on est sûr que notre component a fini d'être rendu. On peut lancer notre appel API ici : 

// Components/FilmDetail.js
...
import { getFilmDetailFromApi } from '../API/TMDBApi'
class FilmDetail extends React.Component {
...
componentDidMount() {
getFilmDetailFromApi(this.props.navigation.state.params.idFilm).then(data => {
this.setState({
film: data,
isLoading: false
})
})
}
}

À présent, on va créer une fonction pour rendre le film s'il n'est pas non défini. Il y aura beaucoup d'éléments à afficher, à tel point que l'écran de votre device ne suffira pas.

Lorsque l'on a beaucoup d'éléments à afficher dans une vue, on n'utilise pas une View, mais une ScrollView.  

L'implémentation de notre fonction pour rendre le film et afficher notre ScrollView donne :

// Components/FilmDetail.js
import React from 'react'
import { StyleSheet, View, Text, ActivityIndicator, ScrollView } from 'react-native'
import { getFilmDetailFromApi } from '../API/TMDBApi'
class FilmDetail extends React.Component {
constructor(props) {
super(props)
this.state = {
film: undefined,
isLoading: true
}
}
componentDidMount() {
getFilmDetailFromApi(this.props.navigation.state.params.idFilm).then(data => {
this.setState({
film: data,
isLoading: false
})
})
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
)
}
}
_displayFilm() {
if (this.state.film != undefined) {
return (
<ScrollView style={styles.scrollview_container}>
<Text>{this.state.film.title}</Text>
{/* Pour l'instant je n'affiche que le titre, je vous laisserais le soin de créer la vue. Après tout vous êtes aussi là pour ça non ? :)*/}
</ScrollView>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
{this._displayFilm()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
scrollview_container: {
flex: 1
}
})
export default FilmDetail

Et côté application :

Affichage du titre du film depuis l'API détail
Affichage du titre du film depuis l'API détail

Ça va, jusque-là ? Pas de surprise, on continue. :pirate:

Affichez le détail d'un film

Cette fois, c'est à vous de jouer. Je vous propose de créer vous-même la vue du détail du film. Vous pouvez partir du code partagé juste au-dessus. Je vous demande de réaliser ceci :

Vue détail d'un film complète
Vue détail complète d'un film

Vous pensez que c'est jouable ? o_O

Je vous aide un peu, histoire que vous ne bloquiez pas. Voici le JSON complet retourné par l'API détail du film :

{
"adult":false,
"backdrop_path":"/c4zJK1mowcps3wvdrm31knxhur2.jpg",
"belongs_to_collection":{
"id":10,
"name":"Star Wars - Saga",
"poster_path":"/ijAC17I7X4xCrKxtp1JUc2YBGuM.jpg",
"backdrop_path":"/d8duYyyC9J5T825Hg7grmaabfxQ.jpg"
},
"budget":11000000,
"genres":[
{
"id":12,
"name":"Aventure"
},
{
"id":28,
"name":"Action"
},
{
"id":878,
"name":"Science-Fiction"
}
],
"homepage":"",
"id":11,
"imdb_id":"tt0076759",
"original_language":"en",
"original_title":"Star Wars",
"overview":"Il y a bien longtemps, dans une galaxie très lointaine... La guerre civile fait rage entre l'Empire galactique et l'Alliance rebelle. Capturée par les troupes de choc de l'Empereur menées par le sombre et impitoyable Dark Vador, la princesse Leia Organa dissimule les plans de l’Étoile Noire, une station spatiale invulnérable, à son droïde R2-D2 avec pour mission de les remettre au Jedi Obi-Wan Kenobi. Accompagné de son fidèle compagnon, le droïde de protocole C-3PO, R2-D2 s'échoue sur la planète Tatooine et termine sa quête chez le jeune Luke Skywalker. Rêvant de devenir pilote mais confiné aux travaux de la ferme, ce dernier se lance à la recherche de ce mystérieux Obi-Wan Kenobi, devenu ermite au cœur des montagnes désertiques de Tatooine...",
"popularity":91.599137,
"poster_path":"/yVaQ34IvVDAZAWxScNdeIkaepDq.jpg",
"production_companies":[
{
"name":"Lucasfilm",
"id":1
},
{
"name":"Twentieth Century Fox Film Corporation",
"id":306
}
],
"production_countries":[
{
"iso_3166_1":"US",
"name":"United States of America"
}
],
"release_date":"1977-05-25",
"revenue":775398007,
"runtime":121,
"spoken_languages":[
{
"iso_639_1":"en",
"name":"English"
}
],
"status":"Released",
"tagline":"Il y a bien longtemps dans une galaxie très lointaine...",
"title":"La Guerre des étoiles",
"video":false,
"vote_average":8.1,
"vote_count":8065
}

Ensuite, plusieurs points pourraient être bloquants :

  1. Pour formater la date, j'ai utilisé la librairie moment.js. (librairie à ajouter :  $ npm install --save moment  )

  2. Pour formater le budget, j'ai utilisé la librairie numeral.js. (librairie à ajouter : $ npm install --save numeral

  3. Pour l'image, j'ai utilisé celle du champ  backdrop_path  du JSON. 

Pour le reste, vous devriez pouvoir vous en sortir seul. ;) Bon courage !

La solution :

// Components/FilmDetail.js
import React from 'react'
import { StyleSheet, View, Text, ActivityIndicator, ScrollView, Image } from 'react-native'
import { getFilmDetailFromApi, getImageFromApi } from '../API/TMDBApi'
import moment from 'moment'
import numeral from 'numeral'
class FilmDetail extends React.Component {
constructor(props) {
super(props)
this.state = {
film: undefined,
isLoading: true
}
}
componentDidMount() {
getFilmDetailFromApi(this.props.navigation.state.params.idFilm).then(data => {
this.setState({
film: data,
isLoading: false
})
})
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
)
}
}
_displayFilm() {
const { film } = this.state
if (film != undefined) {
return (
<ScrollView style={styles.scrollview_container}>
<Image
style={styles.image}
source={{uri: getImageFromApi(film.backdrop_path)}}
/>
<Text style={styles.title_text}>{film.title}</Text>
<Text style={styles.description_text}>{film.overview}</Text>
<Text style={styles.default_text}>Sorti le {moment(new Date(film.release_date)).format('DD/MM/YYYY')}</Text>
<Text style={styles.default_text}>Note : {film.vote_average} / 10</Text>
<Text style={styles.default_text}>Nombre de votes : {film.vote_count}</Text>
<Text style={styles.default_text}>Budget : {numeral(film.budget).format('0,0[.]00 $')}</Text>
<Text style={styles.default_text}>Genre(s) : {film.genres.map(function(genre){
return genre.name;
}).join(" / ")}
</Text>
<Text style={styles.default_text}>Companie(s) : {film.production_companies.map(function(company){
return company.name;
}).join(" / ")}
</Text>
</ScrollView>
)
}
}
render() {
return (
<View style={styles.main_container}>
{this._displayLoading()}
{this._displayFilm()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
scrollview_container: {
flex: 1
},
image: {
height: 169,
margin: 5
},
title_text: {
fontWeight: 'bold',
fontSize: 35,
flex: 1,
flexWrap: 'wrap',
marginLeft: 5,
marginRight: 5,
marginTop: 10,
marginBottom: 10,
color: '#000000',
textAlign: 'center'
},
description_text: {
fontStyle: 'italic',
color: '#666666',
margin: 5,
marginBottom: 15
},
default_text: {
marginLeft: 5,
marginRight: 5,
marginTop: 5,
}
})
export default FilmDetail

Alors, vous vous en êtes sorti ? :soleil: À vrai dire, pour la hiérarchie de vos components et les styles, il y a toujours plusieurs solutions possibles. Selon votre vision, l'expérience, beaucoup de solutions peuvent fonctionner.

Que vous ayez réussi ou non, l'important est d'avoir cherché. Même si, dans ce cours, on voit beaucoup d'éléments et de concepts, dans vos projets personnels, vous allez être confronté à des problèmes que l'on n'a pas traités ici. En cherchant par vous-même, vous acquerrez une certaine autonomie, indispensable dans le monde du développement.

Profitez-en également pour lancer l'application et tester le fonctionnement des ScrollView. Vous verrez que, sur certains films, tout le contenu rentre dans l'écran et la vue n'est pas "scrollable", alors que, sur d'autres films, le contenu dépasse et, automatiquement, la vue devient "scrollable".

Dans le chapitre précédent, tu disais que, pour les développeurs issus du monde mobile, ce chapitre serait facile. Pourquoi ? 

Les cycles de vie existent depuis toujours dans le développement mobile. Selon les technologies, les cycles sont différents, les étapes aussi, mais le principe reste le même. Si vous êtes un développeur mobile et que vous connaissez le développement natif, vous avez dû reconnaître les  viewDidLoad  ,  viewWillAppear  ,  viewDidAppear  ... propres à iOS et les  onCreate  ,  onStart  ,  onResume  ... propres à Android. ^^

On arrive à la fin de ce chapitre centralisé sur les cycles de vie en React. Même si on n'a pas détaillé ensemble toutes les étapes des cycles de vie, vous savez au moins comment cela fonctionne. Vous êtes capable de réaliser des opérations à des moments précis sur un component. Gardez bien le schéma des cycles de vie en tête.

Là, c'est le moment de faire une petite pause. Si si, j'insiste. :lol: Dans le prochain chapitre, on aborde un sujet très intéressant, mais aussi plus complexe. Il s'agit de Redux. Je ne vous en dis pas plus pour l'instant, je garde le suspens. Mais ne vous inquiétez pas, on va y aller ensemble et en douceur.

Exemple de certificat de réussite
Exemple de certificat de réussite