Architecture UI Frontend — MVC, MVP, MVVM, Flux
Un compte rendu de l'apparition des modèles d'architecture UI (MVC · MVP · MVVM · Flux), avec leurs avantages et inconvénients, et une analyse comparative.
Un compte rendu de l'apparition des modèles d'architecture UI (MVC · MVP · MVVM · Flux), avec leurs avantages et inconvénients, et une analyse comparative.
Le mot de passe saisi sert à ouvrir, modifier et supprimer les commentaires secrets.
Lorsque les projets frontend s'agrandissent, le problème se déplace de « comment implémenter » à « où gérer l'état et comment le faire circuler ». Les fonctionnalités fonctionnent, mais si le flux de changement d'état devient flou, le débogage, l'expansion et la collaboration deviennent difficiles simultanément.
MVC, MVP, MVVM, Flux sont des modèles conçus pour résoudre ce problème, mais chaque modèle, en résolvant les problèmes de son prédécesseur, a créé de nouveaux défis, perpétuant ainsi cette évolution.
| Modèle | Problème résolu | Nouveau problème |
|---|---|---|
| MVC | Séparation des rôles | Controller s'alourdit |
| MVP | Simplification de la Vue, facilité de test | Presenter s'alourdit |
| MVVM | Productivité, UI déclarative | Difficulté de suivi de l'état |
| Flux | Flux d'état prévisible | Complexité accrue du code |
Un modèle introduit pour la première fois dans les années 1970 avec Smalltalk. Il divise l'application en trois rôles : Modèle (données, logique métier), Vue (rendu de l'interface) et Contrôleur (gestion des saisies utilisateur).
Le Contrôleur met à jour le Modèle et transmet les données à la Vue, qui les traite. Adapté aux architectures côté serveur où les demandes sont traitées et renvoient du HTML, utilisé par Rails, Django, Spring MVC.
Le problème est que le Contrôleur doit connaître trop de choses. Les saisies utilisateur, l'état du Modèle et ce qu'il faut transmettre à la Vue sont tous centralisés dans le Contrôleur. Plus il y a d'écrans, plus le Contrôleur s'alourdit et les dépendances entre Vue et Modèle se complexifient. En passage côté client, ce problème devient plus évident.
Un modèle introduit pour résoudre le problème du Contrôleur dans MVC. Se compose de Modèle (données, logique), Vue (seulement le rendu), Présentateur (gère toute la logique de l'UI).
La différence fondamentale avec MVC est que la Vue ne fait que le rendu. Dans MVC, la Vue traite elle-même les données reçues, tandis que dans MVP, la Vue transmet les saisies utilisateur au Présentateur et suit ses instructions.
Comme la Vue est totalement passive, le Présentateur peut être testé indépendamment sans UI. Cela signifie qu'on peut vérifier « si le Modèle est correctement mis à jour lors de saisies » sans Vue. Reste un modèle utile lorsque des tests précis sont nécessaires ou quand il faut maintenir la Vue simple.
En développement Android initial, cela était utilisé à cause de la facilité de test. En effet, l'Activity/Fragment dans Android joue à la fois les rôles de Vue et de Contrôleur et tend à s'alourdir, mais en séparant le Présentateur avec MVP, l'Activity ne s'occupe que du rendu et la logique peut être testée rapidement avec JUnit.
En revanche, sur iOS, UIKit poussait MVC comme modèle officiel, entraînant de réels problèmes de Massive View Controller. MVVM avec RxSwift a donc été largement utilisé. Le passage à SwiftUI a fait de MVVM le standard de facto.
Cependant, comme tous les flux se concentrent dans le Présentateur, ce dernier tend aussi à s'alourdir.
C'est un modèle conçu par Microsoft pour WPF (Windows Presentation Foundation, le framework d'interface utilisateur des applications de bureau Windows). La structure Modèle (données et logique), Vue (interface), Modèle de Vue (conserve l'état nécessaire à la Vue) est mise en avant.
L'idée maîtresse est d'éviter que le « Presenter donne des instructions directes à la Vue », mais plutôt de faire en sorte que, lorsqu'on change l'état, la Vue réagisse automatiquement. Le Modèle de Vue détient l'état nécessaire pour la Vue, laquelle lie cet état et se met à jour automatiquement.
Au lieu de contrôler directement « comment mettre à jour la Vue », on déclare « l'état doit changer ainsi ». React, SwiftUI, et Jetpack Compose suivent tous cette idée.
Cela améliore la productivité et le code devient succinct, mais il devient difficile de suivre « pourquoi cet état a-t-il cette valeur? » car on peut changer l'état depuis n'importe où. Quand il y a beaucoup de composants et que l'état commence à circuler à plusieurs niveaux, ce problème devient évident.
C'est une architecture annoncée par Facebook en 2014 avec React. Cela répondait à un problème précis : un bug dans l'application Facebook faisait que le nombre de notifications était mal affiché. Comme l'état pouvait être modifié de plusieurs endroits, il était trop difficile de trouver la cause, d'où l'idée de fixer un chemin unique de changement d'état.
Action → Dispatcher → Store → Vue → (retour à Action)Pour changer l'état, il faut impérativement envoyer une Action. Le Store reçoit cette Action, met à jour l'état, et la Vue s’abonne à cet état mis à jour. Il n'existe pas de chemin direct pour que la Vue modifie l'état.
dispatch({ type: 'ADD_COMMENT', payload: text });Comme tout changement d'état ne passe que par une Action, il est facile de suivre dans quel ordre les choses se produisent. C'est grâce à ce modèle que le Time Travel Debugging de Redux DevTools est possible. L'inconvénient est que le processus est contraignant. Même pour un simple changement d'état, il faut définir une Action, écrire un Réducteur, et connecter au Store, ce qui constitue une suringénierie dans les petits projets.
React n'est ni MVVM ni Flux. Appeler setState donne l'impression de la liaison de données avec une mise à jour automatique de l'UI, mais ce que fait React est différent.
React n'impose aucun mode de gestion d'état. On peut l'utiliser comme MVVM avec useState, ou avec Redux ou Zustand comme Flux. Étant donné que le moteur de rendu et la gestion d'état sont séparés, les deux sont possibles.
Vue avait une approche différente. Jusqu'à Vue 2, Vuex était mise en avant comme bibliothèque officielle de gestion d'état, suivant strictement l'architecture Flux. Un flux unidirectionnel composé de State, Mutation, Action, et Getter était imposé. Avec Vue 3, Pinia est devenu l'alternative officielle, éliminant la Mutation de Vuex et intégrant l'Action, afin de l'alléger, tout en gardant l'essence du flux unidirectionnel de Flux.
Zustand est une bibliothèque utilisée avec React qui permet de suivre les deux modèles. On peut paramétrer directement l'état n'importe où, comme MVVM, ou définir explicitement un schéma d'Actions, comme Flux. Plus léger que Redux, avec moins de boilerplate, il est souvent adopté dans des situations où « Flux est nécessaire mais Redux est trop lourd ».
De nos jours, il est courant de gérer l'état de manière segmentée selon sa nature, plutôt que de l'unifier en un seul pattern.
| Type d'état | Exemple | Méthode de gestion |
|---|---|---|
| État serveur | Données de réponse API | TanStack Query |
| État UI | Ouverture/fermeture d'une modale, toggle | useState |
| État global | Informations d'authentification, thème | Zustand / Redux |
Mettre l'état serveur dans le store global complique la stratégie de mise en cache, et élever l'état UI au niveau global entraîne des rerenderings inutiles. L'idée de « gérer tous les états en un seul endroit » n'est plus une approche efficace.
Si l'accent est mis sur la réactivité rapide de l'état UI et la gestion de l'état par composant, la méthode MVVM est plus naturelle. Dans des cas de petite envergure, de prototypage rapide, où l'UI déclarative est importante, il est judicieux de commencer avec useState. En revanche, si le flux de modification de l'état doit être strictement contrôlé ou s'il existe un état global complexe partagé par plusieurs composants, le pattern Flux est approprié. En résumé, pour un état simple, utilisez MVVM, pour un état global complexe, utilisez Flux.
En pratique, il est fréquent de commencer avec useState, puis d'introduire partiellement Zustand lorsque la localisation de l'état devient difficile à déterminer ou que la reproduction des bugs devient ardue. Il n'est pas nécessaire de choisir la solution parfaite dès le départ; vous pouvez faire la transition à partir des points problématiques.
Une question détermine le moment du changement de pattern : « Le flux d'état actuel est-il contrôlable ? »
function addComment(text) {
model.add(text); // Mise à jour du Modèle
view.render(model.comments); // Transmission des données à la Vue → La Vue traite le rendu
}// Vue — Transmet les saisies et ne réalise que le rendu
button.onClick(() => presenter.handleAdd(input.value));
showComments(comments) { list.innerHTML = render(comments); }
// Présentateur — Gère toute la logique de l'UI
handleAdd(text) {
this.model.add(text);
this.view.showComments(this.model.comments); // Dirige directement la Vue
}const [comments, setComments] = useState([]);
const handleAdd = (text) => {
setComments(prev => [...prev, text]); // Seul changement d'état; la Vue réagit automatiquement
};Le récit d'une aventure intense de 7 semaines : de l'amélioration de l'onboarding et du monitoring d'infrastructure à la communication temps réel et l'optimisation de personnages 3D.
Exploration des avantages et inconvénients de la programmation fonctionnelle et orientée objet, et des raisons du choix fonctionnel dans React
Un compte rendu de l'apparition des modèles d'architecture UI (MVC · MVP · MVVM · Flux), avec leurs avantages et inconvénients, et une analyse comparative.
Enregistrement des problèmes rencontrés lors de l'intégration de personnages dans Three.js pour le projet funda et mon portfolio personnel
Un parcours de 7 mois, des bases au projet de groupe : acquérir les fondamentaux de l'informatique, maîtriser l'ingénierie de l'IA et découvrir l'essence de la conception logicielle.
Le récit d'une aventure intense de 7 semaines : de l'amélioration de l'onboarding et du monitoring d'infrastructure à la communication temps réel et l'optimisation de personnages 3D.
De l'idéation à l'implémentation du MVP en passant par les retours seniors : récit de la mise en place des fondations du projet et des défis techniques relevés
Une analyse de l'essence de la normalisation à travers la logique de mouvement dans les jeux et l'application de l'échelle dans Blender.
Retour sur les 6 semaines de sprint de groupe : défis d'architecture, dynamiques de collaboration et progression technique suite aux retours seniors.
Retour sur dix semaines d’apprentissage au Boostcamp : technologies étudiées, défis de conception, fatigue mentale et enseignements tirés de l’utilisation de l’IA.
Un retour sur la phase Challenge du Boostcamp : missions quotidiennes, apprentissage CS, feedback entre pairs, travail d’équipe et évolution de ma façon d’apprendre avec l’IA.
Exploration des avantages et inconvénients de la programmation fonctionnelle et orientée objet, et des raisons du choix fonctionnel dans React