Aller au contenu principal


Utiliser les "props"

Cette page analyse les portions de code qui figurent dans les onglets en bas de page. On y décortique les composants et leurs propriétés (ou props dans le jargon) et 2 manières différentes de produire un rendu répétitif.
Ces codes produisent le même rendu qu' illustre l'image ci-dessous.

Cliquez pour agrandir l'image

 

Billet créé le :
07 déc 2023

Les importations :

La ligne de code : 

import Reac, {useState} fron 'react';

permet de gérer le changement d'état du composant suite aux actions de l'utilisateur (relire cet article sur 'useState').

La ligne de code :

import {StyleSheet,
       Text,
       TouchableOpacity,
       Dimensions,
       View } from "react-native";

permet l'emploi dans mon composant des composants natifs StyleSheet, Text, TouchableOpacity, Dimensions, View.

Les props

L'image ci-dessus affiche 5 boutons qui se ressemblent énormément et qui produisent des effets très semblables. Pour réaliser cette vue, on peut donc  soit : 

  • dupliquer 5 fois le code du composant "bouton" en modifiant à chaque fois les attributs qui le caractérisent : par exemple le label et l'action réalisée ; 

  • placer le code du composant à répéter dans une "boucle" et le paramétrer en utilisant les "props" nécessaire pour le caractériser. 

Cette 2nde option, généralement plus rapide à écrire et surtout plus facile à maintenir, est utilisée dans les codes analysés dans cette page.

Supposons que les seuls éléments caractéristiques des boutons à afficher soient son label et son action et que le composant affichant un bouton s'intitule "MonBouton".  Ce bouton "caractérisé" le sera par les props "label" et "action" et le code pour l'afficher aura la forme suivante :

<MonBouton label="A" action="1" >
        ....
</MonBouton>

Dans la fonction, qui crée ce composant caractérisé à l'aide de "props", il faut ajouter ses "props" en arguments. Par exemple, pour le composant "MonBouton" on disposera des syntaxes suivantes :

  1. const MonBouton = props => { ... }

  2. const MonBouton = (props) => { ... }

  3. const MonBouton = ({label , action}) => { ... }

Dans les syntaxes 1 et 2, l'accès à la valeur d'une "props" utilise la syntaxe 

  • {props.label} pour la props "label";
  • {props.action} pour la props "action".

Dans la syntaxe 3 (que je préfère), la syntaxe est {label} pour la valeur de la props "label" et {action} pour la valeur de la props "action" ;

 

Attention dans la syntaxe 1 ne peut être utilisée que lorsque "props "est le seul argument de la fonction.

const MaPage =(p1, p2) => {
       //les instructions  pour les fonctionnalités du composant
        return(
        //les instructions qui forment le rendu (la vue)
        );

Le nom d'un composant débute obligatoirement par une lettre majuscule et,  utilise la syntaxe des fonctions fléchées qui est rappelée ci-dessus.

"LesBoutons" de la version 1 :

Dans cette version, "LesBoutons" est une fonction recevant les arguments "value" (le label du bouton) et "leSetter" (l'action que réalise le bouton). Ce sont des caractéristiques d'un bouton.

Une relecture du billet "boucles et rendu" pourra être très utile pour la compréhension de la suite de cette page.

Le code ci-dessous montre que les boutons sont "créés" dans une boucle for et stockés dans le tableau data2. 

const LesBoutons = (value,  leSetter) => {
    var data2 = [];
        for (let i=0; i<5; i++) {
            data2.push(
                <UnBouton  key={values[i]}  value = {value} newValue = { values[i]}  leSetter = {leSetter } />
            );
        }
    return (data2);
};

 Chaque bouton est caractérisé par les props :

  • key (cf. la page "boucles et rendu") ;
  • value : la lettre affichée actuellement sur le bouton ie : son label ;
  • newValue : un élement du tableau values (ie : ["A", "B", "C", "D","E"]) ;
  • leSetter : la méthode setValue.

UnBouton est défini par la fonction -composant dont le code suit :

const UnBouton = ({value, newValue, leSetter}) => {
    return(
        <TouchableOpacity
          onPress={() => leSetter(newValue)}
          style={[
            styles.button,
            newValue === value && styles.selected,
          ]}
        >
            <Text
                style={[
                  styles.buttonLabel,
                  newValue === value && styles.selectedLabel,
                ]}
            >
                {newValue}
            </Text>
        </TouchableOpacity>
    )
};

La ligne de code

style={[
styles.button,
newValue === value && styles.selected,
]}

correspond à la mise en place d'un style conditionnel
En effet, les styles peuvent être appliqués de manière conditionnelle à l'aide soit :
  • de l'opérateur ternaire "?" 
  • d'une expression booléenne (dite court_circuit ou "lazy evaluation") utilisant :
    • le ET logique &&, comme dans notre cas  ;
    • le OU logique || .
  • d'une fonction qui renvoie un objet style.
Dans notre cas, l'évaluation de "newValue === value && styles.selected" rendra soit :
  • "Vrai &&style.selected" donc "style.selected"  ;
  • faux (et sera ignorée par React).

"LesBoutons" de la version 2 :

Dans cette version, les boutons sont rendus par la fonction JS map() appliquée au tableau values.

Une relecture du billet "boucles et rendu" pourra être très utile pour la compréhension de la suite de cette page.

L'argument { children, label,  values, selectedValue, leSetter } de cette fonction-composant définit l'ensemble des propriétés (les props)  qui  caractérisent ce composant. Son code figure ci-dessous :

const LesBoutons = ({
    children,
    label,
    values,
    selectedValue,
    leSetter,
    }) => {
    return (
        <View style={{ padding: 10, flex: 1 }}>
            <Text style={styles.label}>{label}</Text>
            <View style={styles.containerButton} >
              {values.map((val) => ( ....)}
            </View>
            <View style={[
                styles.containerBox,
                ]}>
                {children}
            </View>
        </View>
    );
};

 

Ce composant "LesBoutons" rend la vue formée par l'imbrication de  :

  • un composant "Text" qui affiche le titre de la page ( évaluation de la propriété "label") ;

  • un composant "View" qui affiche, dans le style "containerButton", les différents boutons :

    • générés  par l'exécution de la fonction javascript,  map() de syntaxe : array.map(function)  sur le tableau "values"  qui vaut [A,B,C,D,E]. 
      Donc, pour chacun des éléments de ce tableau,  la fonction anonyme argument de map() crée un bouton par le composant "touchableOpacity". 
      Cette fonction anonyme correspond au code suivant :

      (val) => (
                      <TouchableOpacity
                            key={val}
                           onPress={() => leSetter(val)}
                           style={[
                               styles.button,
                               selectedValue === val && styles.selected,
                              ]}
                      >
                              <Text
                                      style={[
                                          styles.buttonLabel,
                                          selectedValue === val && styles.selectedLabel,
                                    ]}
                              >
                                  {val}
                             </Text>
                      </TouchableOpacity>
                    )

    • le paramètre "val"  de cette fonction anonyme prend donc successivement la valeur de chacun des éléments du tableau "values". Par exemple  :
      • pour l'élément "A" du tableau "values" :
        • val vaut "A" ; 
        • le bouton créé, portera l'intitulé "A" ;
        • l'action onPress sera la fonction anonyme : () => {setValue("A")}.
      • de même, pour la valeur "B" du tableau "values" : 
        • val vaut "B" ;
        • le bouton créé, portera l'intitulé "B" ;
        • l'action onPress sera la fonction anonyme : () => {setValue("B")}.
      • ...
    • lors d'un appui sur un bouton, par exemple le bouton "B", l'action effectuée est  setValue("B") :
      • la valeur "B" sera affectée à la variable "value" et une nouvelle exécution de "MaPage" sera lancée (voir le billet : le hook useState) et  les propriétés du composant "LesBoutons" auront pour nouvelles valeurs :
        • label : "Clique sur un bouton" ;
        • values : {["A", "B", "C", "D","E",]} ;
        • selectedValue :"B" ie la valeur {value} ;
        • leSetter : le setter "setValue".
      • Au l'initialisation du programme, value vaut "D", le bouton, par son composant Text affiche le label "D". Il a les styles "button" et "selected" comme expliqué dans l'alerte sur les styles conditionnels figurant à la fin du paragraphe précédant. Son label aura les styles"buttonLabel" et "selectedLabel". Les autres auront uniquement le style "button"  et leur label, le style "buttonLabel" ;
      • De même, si le  bouton vient de recevoir l’interaction de l'utilisateur, il aura aussi les styles "button" et "selected", sinon uniquement le style "button"  ;
      • De même, si le  bouton vient de recevoir l’interaction de l'utilisateur, son label (qui sera l'évaluation de "value") aura les styles"buttonLabel" et "selectedLabel", sinon uniquement le style "buttonLabel".
  • un 2nd composant "View" qui affiche l'évaluation du paramètre "children" (cf § MaPage : le composant-parent ci-dessous). C' est :

    • une vue formée de 3 composants "Text" et qui affichera "le bouton "X" a été pressé" ou "X" est l'évaluation du paramètre "value".

MaPage : Le composant racine de ces codes :

Les codes présentés pour définir cette fonction-composant nommée "MaPage" sont très voisins. 

La ligne const [value, setValue] = useState("D"); définit la variable d'état "value", son setter "setValue" et initialise "value" avec la valeur "D". Relire la page "react-native : le hook useState" pour connaitre le rôle des variables d'état et leur effet sur le rendu du composant.

MaPage affiche donc une vue réalisée par le composant natif "View". Dans la version 1, c'est le conteneur du composant  "LesBoutons"et d'un composant "View" contenant de 3 composants natifs "Text" ; dans la version 2,  c'est le conteneur du seul composant  "LesBoutons".

Dans la version 1, les boutons sont produit par l'exécution du code :

{ LesBoutons( value, setValue ) }

Il s'agit donc de l'évaluation de la réponse fournie par la fonction (ordinaire !) LesBoutons( ) lorsqu'on lui passe les arguments "value" et "setValue".

Dans la version 2, les boutons (et plus...) sont produit par l'exécution du code :

<LesBoutons
   label="Clique sur un bouton"
   values={["A", "B", "C", "D","E",]}
   selectedValue={value}
   leSetter={setValue}
>
    <View
        style={[styles.box,]} >    
        <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}> Le bouton</Text>
        <Text style = {{color:'black', fontSize:28, fontWeight: 'bold'}}> {value} </Text>
        <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}>   a été pressé  </Text>
    </View>
</LesBoutons> 

Les propriétes (props) du composant "LesBoutons" :

  • "leSetter" a  pour valeur  "setValue", le setter de "value" (cf : la page useState) ;

  • "selectedValue" a  pour valeur celle de la variable "value" (dont la valeur initiale est "D" grâce à l'instruction const [value, setValue] = useState("D"); ) ;

  • "values" est le tableau ["A", "B", "C", "D", "E"]   ;

  • "label" est la chaîne "Clique sur un bouton".

Comme "LesBoutons" est "enfant de ""MaPage". Il dispose d'une propriété particulière (native ou technique) : "children" qui dès lors que ce composant est un conteneur, a pour valeur le(s) composant(s) emboité(s) (ie : "children"). Dans le composant "LesBoutons", cette valeur correspond au composant "View" (formé des 3 composants "Text"). Celui-ci affichera dans le style "containerBox", "le bouton D a été pressé" lors du 1er affichage de "MaPage".

La propriété spéciale "children" est utilisée pour transmettre tout ce qui se situe entre les balises d'ouverture et de fermeture lors de l'appel du composant. Dans "LesBoutons", c'est donc ce qui se trouve entre <LesBoutons> et </LesBoutons>. Elle est initialisée automatiquement.

Le style

Dans React-native, la mise ne page s'appuie sur la technologie FlexBox. Les styles sont les éléments d'un tableau (ici nommé "styles") produit par le ligne de code  :

const styles = StyleSheet.create({...})

Le style "page" est définit par  :

    page: {
        flex:1,
        height: screenHeight *4/ 5,
        width: screenWidth,
        flexDirection: "column",
    },

"styles.page"  est un "bloc - boite" caractérisé par : 

  • largeur : toute la largeur de l'écran ;

  • hauteur : les 4/5 de la hauteur de l'écran ;

  • tous les blocs contenus s'empileront dans une colonne, ici ces blocs sont :

    • un composant "Text" ;

    • le bloc-boite  "containerButton" (emplacement 1 de l'image)  ;

    • le bloc-boite "containerBox" (emplacement 2 en vert).

  "style.containerButton" est produit par le code  :

  containerButton: {
        flexDirection: "row",
        flexWrap: "wrap",
    },

Ses composants "TouchableOpacity" seront affichés sur une ligne (flexDirection: "row") avec passage à la ligne suivante si nécessaire (flexWrap: "wrap").

  "style.containerBox" est produit par le code  :

    containerBox: {
        flex: 1,
        marginTop: 8,
        backgroundColor:"#E0FFE8",
        flexDirection: "row",
        justifyContent: "center",
        alignItems: "center",
    },

Son composant "View" (issu de l'évaluation de "children") sera centré verticalement et horizontalement (justifyContent: "center", alignItems: "center").

Ce composant "View" contient 3 composants "Text" qui seront affichés en colonne et  centrés verticalement et horizontalement dans une boite de dimensions screenWidth /2 par screenHeight /3 par le style "box" suivant  :

    box : {
        width: screenWidth /2,
        height: screenHeight /3,
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#96C8FF",
    },

Les constantes "screenWidth" et "screenHeight" sont définies par :

const screenWidth= Dimensions.get("screen").width;
const screenHeight = Dimensions.get("screen").height;

Les codes analysés :

Cliquez sur le bouton pour copier le code
dans le presse papier  
    
import React, { useState } from "react";
import {StyleSheet,
Text,
TouchableOpacity,
Dimensions,
View } from "react-native";
//       
const screenWidth= Dimensions.get("screen").width;
const screenHeight = Dimensions.get("screen").height;   
//
const values = ["A", "B", "C", "D","E"];
/*
* le composant principal MaPage
*/
const MaPage = () => {
   const [value, setValue] = useState("D");
   //
   return (
       <View style={[ styles.page,] }>
           <View style={{ padding: 10, flex: 1}}>
               <Text style={styles.label}> "Clique sur un bouton"</Text>
               <View style={[styles.containerButton,]}>
                   { LesBoutons( value, setValue ) }
               </View>
           </View>
           <View style={[styles.containerBox, {flex: 3}]}>
               <View style={[styles.box,]} >   
                   <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}> Le bouton</Text>
                   <Text style = {{color:'black', fontSize:28, fontWeight: 'bold'}}> {value} </Text>
                   <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}> a été pressé </Text>
               </View>
           </View>
       </View>
   );
};
//
const LesBoutons = (value, leSetter) => {
   var data2 = [];
       for (let i=0; i<5; i++) {
           data2.push(
               <UnBouton key={values[i]} value = {value} newValue = { values[i]} leSetter = {leSetter } />
           );
       }
   return (data2);
};
//
const UnBouton = ({value, newValue, leSetter}) => {
   return(
       <TouchableOpacity
        onPress={() => leSetter(newValue)}
        style={[
           styles.button,
           newValue === value && styles.selected,
        ]}
       >
           <Text
               style={[
                styles.buttonLabel,
                newValue === value && styles.selectedLabel,
               ]}
           >
               {newValue}
           </Text>
       </TouchableOpacity>
   )
};
/*
* les styles
*/
const styles = StyleSheet.create({
   containerBox: {
       flex: 1,
       marginTop: 8,
       backgroundColor:"#E0FFE8",
       flexDirection: "row",
       justifyContent: "center",
       alignItems: "center",
   },
   box: {
       width: screenWidth /2,
       height: screenHeight /3,
       flexDirection: "column",
       justifyContent: "center",
       alignItems: "center",
       backgroundColor: "#96C8FF",
   },
   containerButton: {
       flexDirection: "row",
       flexWrap: "wrap",
   },
   button: {
       paddingHorizontal: 8,
       paddingVertical: 6,
       borderRadius: 4,
       backgroundColor: "oldlace",
       alignSelf: "flex-start",
       marginHorizontal: "1%",
       marginTop:6,
       marginBottom: 6,
       minWidth: "30%",
       textAlign: "center",
   },
   selected: {
       backgroundColor: "coral",
       borderWidth: 0,
   },
   buttonLabel: {
       fontSize: 20,
       fontWeight: "500",
       color: "coral",
       textAlign: "center",
   },
   selectedLabel: {
       color: "white",   
   },
   label: {
       textAlign: "center",
       marginTop: 20,
       marginBottom: 20,
       fontSize: 24,
       color: "black",
   },
   page: {
       flex:1,
       height: screenHeight *4/ 5,
       width: screenWidth,
       flexDirection: "column",
   },
});
//
export default MaPage;

 

               

Cliquez sur le bouton pour copier le code
dans le presse papier  
    
import React, { useState } from "react";
import {StyleSheet,
Text,
TouchableOpacity,
Dimensions,
View } from "react-native";
//       
const screenWidth= Dimensions.get("screen").width;
const screenHeight = Dimensions.get("screen").height;   
/*
* le composant principal MaPage
*/
const MaPage = () => {
   const [value, setValue] = useState("D");
   return (
       <View style={[ styles.page,] }>
           <LesBoutons
            label="Clique sur un bouton"
            values={["A", "B", "C", "D","E",]}
            selectedValue={value}
            leSetter={setValue}
           >
           <View
               style={[styles.box,]} >   
               <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}> Le bouton</Text>
               <Text style = {{color:'black', fontSize:28, fontWeight: 'bold'}}> {value} </Text>
               <Text style = {{color:'black', fontSize:14, fontWeight: 'bold'}}> a été pressé </Text>
           </View>
           </LesBoutons>
       </View>
   );
};
//
const LesBoutons = ({
   children,
   label,
   values,
   selectedValue,
   leSetter,
   }) => {
   return (
       <View style={{ padding: 10, flex: 1 }}>
           <Text style={styles.label}>{label}</Text>
           <View style={styles.containerButton} >
            {values.map((val) => (
               <TouchableOpacity
                key={val}
                onPress={() => leSetter(val)}
                style={[
                   styles.button,
                   selectedValue === val && styles.selected,
                ]}
               >
                <Text
                   style={[
                    styles.buttonLabel,
                    selectedValue === val && styles.selectedLabel,
                   ]}
                >
                   {val}
                </Text>
               </TouchableOpacity>
            ))}
           </View>
           <View style={[
               styles.containerBox,
               ]}>
               {children}
           </View>
       </View>
   );
};
/*
* les styles
*/
const styles = StyleSheet.create({
   containerBox: {
       flex: 1,
       marginTop: 8,
       backgroundColor:"#E0FFE8",
       flexDirection: "row",
       justifyContent: "center",
       alignItems: "center",
   },
   box: {
       width: screenWidth /2,
       height: screenHeight /3,
       flexDirection: "column",
       justifyContent: "center",
       alignItems: "center",
       backgroundColor: "#96C8FF",
   },
   containerButton: {
       flexDirection: "row",
       flexWrap: "wrap",
   },
   button: {
       paddingHorizontal: 8,
       paddingVertical: 6,
       borderRadius: 4,
       backgroundColor: "oldlace",
       alignSelf: "flex-start",
       marginHorizontal: "1%",
       marginTop:6,
       marginBottom: 6,
       minWidth: "30%",
       textAlign: "center",
   },
   selected: {
       backgroundColor: "coral",
       borderWidth: 0,
   },
   buttonLabel: {
       fontSize: 20,
       fontWeight: "500",
       color: "coral",
       textAlign: "center",
   },
   selectedLabel: {
       color: "white",   
   },
   label: {
       textAlign: "center",
       marginTop: 20,
       marginBottom: 20,
       fontSize: 24,
       color: "black",
   },
   page: {
       flex:1,
       height: screenHeight *4/ 5,
       width: screenWidth,
       flexDirection: "column",
   },
});
//
export default MaPage;

 

 

  •