By Morgan Guimard/23 avril 2025/Commentaires fermés sur La gestion des états dans React
React est une librairie JavaScript dédiée à la constructions d’ interfaces pour des applications web.
L’un des plus gros challenges dans une application React est d’afficher des données de manière efficace et efficiente. Plus l’application grandit, plus il devient difficile d’orchestrer correctement tous les composants tout en évitant les re-rendus inutiles.Nous allons voir, au cours de cet article, les différents moyens de partager de l’information entre les composants d’une application React. Nous verrons aussi les pièges à éviter pour faciliter la gestion des états dans React.
UseState et prop-drilling
Le flux de données dans React est unidirectionnel. Cela signifie que les données circulent dans un seul sens : des composants parents vers les composants enfants. L’objectif est de favoriser une architecture de données propre et prévisible.
Les props permettent de transmettre des données des composants parents aux composants enfants. Elles sont en lecture seule dans le composant enfant, ce qui garantit que ce dernier ne peut pas modifier directement ces données.
Le « prop-drilling » désigne le processus de transmission des props à travers plusieurs couches de composants. Certains de ces composants n’utilisent pas directement ces données et ne font que les relayer à leurs propres composants enfants.
Cela peut entraîner des difficultés de débogage, des comportements inattendus, ainsi que des composants étroitement couplés et difficiles à réutiliser.
React Context
Les contextes React permettent à un composant parent de mettre certaines informations à disposition de n’importe quel composant de l’arborescence, quelle que soit sa profondeur. La donnée devient ainsi accessible sans avoir à la transmettre explicitement via des props, ce qui permet de résoudre le problème du prop-drilling.
Un composant « abonné » à un contexte (via le hook useContext) est automatiquement re-rendu lorsqu’une modification intervient dans ce dernier. Cependant, toute modification d’une partie du contexte entraîne le re-rendu de tous les composants abonnés.
On peut alors envisager de créer un contexte par type de donnée afin de les partager dans notre arbre de composants. Mais que se passe-t-il si l’on se retrouve avec 50 contextes différents ? On plonge alors dans l’enfer des contextes.
L’enfer des contextes React survient lorsque l’API Context est utilisée de manière excessive ou inappropriée. C’est comme si votre application devenait un puzzle à trop de couches, rendant l’ensemble difficile à suivre et à maintenir.
Imaginez une situation avec de nombreuses couches, chacune contenant des éléments différents, comme des états ou des fonctions. Cela devient un véritable fouillis, difficile à comprendre, à tester et à maintenir.
Stores Redux
Un store Redux est un emplacement qui contient toutes les données. A l’inverse des contextes React, nous n’avons besoin que d’un seul store, lequel peut être découpé en « slices » (morceaux). La modification d’une partie du store entraîne des re-rendus des composants abonnés à cette partie seulement.
Une mise à jour d’une partie du store est déclenchée depuis n’importe quelle partie de l’arbre des composants. Une action utilisateur est le plus souvent à l’origine du déclenchement. Dans la terminologie Redux, cela s’appelle « dispatch » d’actions.
L’action est envoyée aux « reducers ». Ce sont des fonctions pures qui permettent de modifier l’état. Par exemple, une action « increment » déclenchera la fonction qui incrémente un compteur. Une autre action « addOrder » déclenchera la fonction qui ajoute une commande dans notre liste de commandes, etc.
La fonction reducer est exécutée, puis la partie du store concernée est mise à jour. Finalement, les composants abonnées déclenchent un re-rendu.
Conclusion
Nous avons vu au cours de cet article différentes façons de maintenir des données pour optimiser la gestions des états dans React. Ils est possible de combiner ces trois concepts précédemment étudiés, mais il est nécessaire de savoir quand et comment les utiliser :
Utilisez le prop-drilling lorsque vous souhaitez passer des données à des dumb-components. Ceux-ci se contenteront d’afficher les données passées en paramètres, sans les transmettre à nouveau à d’autres composants.
Utilisez les contextes pour des données de haut niveau qui nécessitent un re-rendu global : gestion du thème, de la langue, des préférences utilisateur, de l’authentification, etc.
Utilisez un store Redux pour gérer les données métier : liste d’utilisateurs, liste de factures, etc.
By Benjamin Rosier/6 août 2024/Commentaires fermés sur Découverte de JetPack Compose
Description
Jetpack Compose est un framework d’interface utilisateur développé par Google, sorti en version 1.0 en août 2021. Comme SwiftUI pour iOS, il permet de faciliter et d’accélérer la création d’interfaces graphiques pour les applications Android. C’est une approche innovante et différente de ce que proposait Android en termes de développement d’UI via les XML Views.
Avantages
Un des principaux avantages de Compose est l’approche déclarative des éléments. Grâce à la gestion de la fonction remember. Lorsqu’un élément change d’état, l’interface se met à jour automatiquement.
Par exemple, lorsque nous déclarons une variable var name by remember { mutableStateOf("Jean") }, la valeur initiale de la variable est Jean, mais lorsque le nom changera, l’interface affichera automatiquement la nouvelle valeur associée.
Les composants sont appelés Composable et peuvent être imbriqués entre eux afin de créer des composants modulables, complexes et réutilisables. Cela permet ainsi de créer un code plus structuré et plus compréhensible pour les développeurs.
De plus, Compose est plus performant grâce à sa capacité à « recomposer » les bons composants au bon moment. C’est-à-dire qu’il est capable de mettre à jour les composants nécessitant un changement d’état sans avoir à tout recréer.
Voulant optimiser et faciliter au maximum le développement d’interfaces utilisateur, Google intègre des Composable favorisant les standards de l’écosystème Android, tels que Scaffold, qui permet de gérer une vue avec une TopBar, une BottomBar, un FloatingActionButton, tout en gérant automatiquement les bonnes marges pour le contenu.
Démonstration
Prenons l’exemple d’une interface affichant une liste de prénoms pouvant être alimentée par un champ de texte.
XML
activity_main.xml
Ce fichier va définir l’interface principale de l’application. Comprenant un EditText pour la saisie d’un nouveau nom, une ImageView pour ajouter l’élément à la liste et une RecyclerView pour afficher celle-ci. Comme la liste peut contenir un nombre indéterminé d’éléments, ce composant est plus adapté et optimisé.
Représentant un élément de la liste, ce fichier comprendra un TextView pour afficher le nom, ainsi qu’une ImageView pour supprimer l’entrée si nécessaire.
Maintenant que les fichiers XML ont été créés, nous devons les associer à leurs fichiers Kotlin. ListAdapter.kt va, comme son nom l’indique, associér l’élément de la liste avec le composant XML
class ListAdapter(
private val names: MutableList,
private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvName: TextView = view.findViewById(R.id.tv_name)
val butDelete: ImageView = view.findViewById(R.id.but_delete)
init {
butDelete.setOnClickListener {
onDeleteClick(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvName.text = names[position]
}
override fun getItemCount(): Int {
return names.size
}
}
class ListAdapter(
private val names: MutableList,
private val onDeleteClick: (Int) -> Unit
) : RecyclerView.Adapter() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvName: TextView = view.findViewById(R.id.tv_name)
val butDelete: ImageView = view.findViewById(R.id.but_delete)
init {
butDelete.setOnClickListener {
onDeleteClick(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvName.text = names[position]
}
override fun getItemCount(): Int {
return names.size
}
}
MainActivity.kt
Maintenant que tout est en place, nous pouvons créer le fichier principal qui gérera toute la logique de la fonctionnalité. Ce fichier associera les différents composants aux variables ainsi que ListAdapter à la RecyclerView. De plus, c’est ici que nous pourrons gérer les actions des utilisateurs, telles que l’ajout et la suppression d’un prénom avec la mise à jour de l’interface.
class MainActivity : AppCompatActivity() {
private lateinit var etName: EditText
private lateinit var butAdd: ImageView
private lateinit var rvNames: RecyclerView
private lateinit var names: MutableList
private lateinit var adapter: ListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
etName = findViewById(R.id.et_name)
butAdd = findViewById(R.id.but_add)
rvNames = findViewById(R.id.rv_names)
names = mutableListOf("Pierre", "Paul", "Jacques")
adapter = ListAdapter(names) { position ->
names.removeAt(position)
adapter.notifyItemRemoved(position)
}
rvNames.setLayoutManager(LinearLayoutManager(this))
rvNames.setAdapter(adapter)
butAdd.setOnClickListener(View.OnClickListener {
val name = etName.getText().toString()
if (name.isNotEmpty()) {
names.add(name)
adapter.notifyDataSetChanged()
etName.setText("")
}
})
}
}
class MainActivity : AppCompatActivity() {
private lateinit var etName: EditText
private lateinit var butAdd: ImageView
private lateinit var rvNames: RecyclerView
private lateinit var names: MutableList
private lateinit var adapter: ListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
etName = findViewById(R.id.et_name)
butAdd = findViewById(R.id.but_add)
rvNames = findViewById(R.id.rv_names)
names = mutableListOf("Pierre", "Paul", "Jacques")
adapter = ListAdapter(names) { position ->
names.removeAt(position)
adapter.notifyItemRemoved(position)
}
rvNames.setLayoutManager(LinearLayoutManager(this))
rvNames.setAdapter(adapter)
butAdd.setOnClickListener(View.OnClickListener {
val name = etName.getText().toString()
if (name.isNotEmpty()) {
names.add(name)
adapter.notifyDataSetChanged()
etName.setText("")
}
})
}
}
Comme nous pouvons le constater dans la MainActivity.kt, l’adaptateur a besoin d’être notifié d’un changement; à la fois lorsqu’on ajoute un élément adapter.notifyDataSetChanged() et lorsqu’on supprime un élément adapter.notifyItemRemoved(position).
Compose
Avec Compose, nous aurons besoin d’un seul fichier. Nous pouvons bien évidemment séparer les composants en plusieurs fichiers pour plus de lisibilité ou d’ergonomie.
La fonction remember nous permet de suivre les changements de valeurs, tant sur la liste des noms que sur la saisie de texte.
Le composable LazyColumn est l’équivalent d’une RecyclerView. Il permet d’optimiser l’affichage d’une liste de taille indéterminée.
Enfin, le composable FirstNameRow représente une entrée dans la liste. Il n’y a pas ici besoin d’adaptateur, il suffit de déclarer le composant tel quel et de l’intégrer dans la boucle de la LazyColumn.
MainActivity.kt
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MainActivity : ComponentActivity(){
overridefunonCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
setContent {
MainView()
}
}
}
@Composable
funMainView(){
var names by remember {mutableStateOf(listOf("Pierre", "Paul", "Jacques"))}
Comme nous pouvons le constater ci-dessus, aucun adaptateur n’a été initié, et aucune notification envers le LazyColumn n’a besoin d’être faite. Tout se fait distinctement, simplement et rapidement.
Nous pouvons également noter qu’il existe une fonction de prévisualisation qui permet d’afficher la vue en temps réel. Nous avons aussi la possibilité d’interagir directement avec les différents composants sans qu’aucun émulateur ou appareil physique ne soit nécessaire. Cela permet d’effectuer des tests d’UI et fonctionnels beaucoup plus rapidement.
Conclusion
Tout comme SwiftUI côté iOS, Jetpack Compose modernise le développement d’applications Android en offrant une approche plus simple, plus puissante et plus concise pour la création d’interfaces utilisateur. Les développeurs bénéficient d’une productivité accrue et d’une meilleure expérience de développement. Pour ma part, je trouve cette approche de développement bien plus intéressante et innovante que le modèle XML traditionnel. De plus, lorsqu’on développe à la fois sur Android avec Kotlin/Compose et sur iOS avec Swift/SwiftUI, nous remarquons des similitudes qui facilitent et accélèrent considérablement le développement sur les deux plateformes concurrentes.
By Benjamin Rosier/5 avril 2024/Commentaires fermés sur UIKit vs SwiftUI
UIKit
Pour développer des applications iOS en utilisant le langage Swift ou Objective-C, Apple propose un framework d’interface utilisateur impératif : UIKit. Il permet de construire la partie UI de l’application notamment via l’InterfaceBuilder, l’outil de développement d’interface graphique intégré dans Xcode.
La prise en main de cet outil peut être fastidieuse, et malgré l’habitude, ajouter un simple écran s’avère assez long. Il est tout d’abord nécessaire de glisser et déposer 1 à 1 chacun des composants, pour ensuite les lier entre eux afin de les positionner. Enfin, il faut toujours procéder de la même manière et déclarer ces derniers dans un ViewController.
Pour certains écrans, parfois complexes, cette méthode est chronophage et répétitive. De plus, les écrans créés par cet outil sont intégrés dans un fichier appelé Storyboard, pouvant contenir de multiples interfaces et donc présenter plusieurs inconvénients :
Un temps de chargement trop long
De nombreux conflits lors de merge (en utilisant Git par exemple)
Pour pallier ces difficultés, ainsi qu’aux nouveaux designs qui peuvent être plus exigeants, et par conséquent nécessiter un temps de développement plus important, en 2019, Apple a proposé un nouveau framework : SwiftUI.
SwiftUI
Ce nouveau framework d’UI propose une approche déclarative et n’est disponible qu’en Swift. La construction d’un écran se déroule directement dans le code, ce qui est beaucoup plus rapide et compréhensible par les développeurs. Construire une simple interface prendra ainsi peu de temps, chaque écran faisant partie d’un unique fichier (à contrario des Storyboards cités auparavant). Il propose également un canvas interactif permettant de visualiser les changements en temps réel selon plusieurs configurations. Une est notamment utilisée pour l’accessibilité, rappelant ainsi les développements web et mobiles hybrides.
Ce framework peut à la fois être intégré dans des composants UIKit pour ainsi disposer de composants plus dynamiques. De par sa jeunesse, il peut également au contraire intégrer des composants UIKit afin de compenser un certain manque d’adaptabilité. N’étant disponible qu’à partir d’iOS13, certaines applications et téléphones restent évidemment incompatibles avec SwiftUI. Cependant, les versions minimales iPhone sont souvent augmentées pour des raisons de sécurité, rendant ainsi SwiftUI potentiellement supporté par tous les iOS dans un futur proche. A contrario d’UIKit, nécessitant la création d’un nouveau d’un projet en AppKit pour développer une application MacOS, SwiftUI prend lui en charge le multi-device de tous les appareils de la pomme.
UIKit vs SwiftUI : démonstration comparative chronométrée
Prenons l’exemple d’un écran constitué simplement d’un titre et d’un texte, possédant une valeur qui s’incrémente ou se décrémente à l’aide de deux boutons. L’approche de conception est bien différente, avec UIKit, le code n’est utilisé que pour la partie logique de l’application. La partie design se fait via un outil d’interface qui permet de construire notre écran au fur et à mesure, de manière plus contrôlée, mais plus lente. Une bonne partie du temps est utilisée pour parcourir les menus des différents composants. Avec SwiftUI, nous restons sur la même page, en utilisant que très peu la souris et le pad, tout se construisant via le code. Les vidéos accélérées ci-dessous démontrent que l’approche SwiftUI est jusqu’à 2,5 fois plus rapide qu’UIKIt dans cet exemple.
UIKit Demo
SwiftUI Demo
Conclusion UIKit vs SwiftUI
Bien que l’utilisation des deux frameworks puissent s’associer, et qu’UIKit prévale de par son ancienneté, SwiftUI se démarque. En effet, son dynamisme, sa facilité de prise en main ainsi que sa rapidité d’écriture et de conception me permettent, après une réelle utilisation sur projet d’affirmer qu’il représente le futur du développement natif Swift.
Précédemment, j’avais pu développer un projet mobile natif Android en Java et iOS en Swift/UIKit. Je me sentais beaucoup plus à l’aise sur Android et retourner sur xCode m’apparaissait fastidieux et pénible. En effet, avec des temps de chargements longs, de simples écrans à construire pouvaient prendre des heures de travail, pour le même rendu final sur Android, en 3 fois moins de temps. Apprendre et utiliser SwiftUI a donc été, sans l’ombre d’un doute, une vraie bouffée d’air frais. Le framework était certes encore jeune, mais tellement puissant, fluide et dynamique qu’il m’a donné à l’époque l’envie de rouvrir xCode !
Nous utilisons des cookies pour vous garantir la meilleure expérience sur notre site web. Si vous continuez à utiliser ce site, nous supposerons que vous en êtes satisfait.Ok