Le design pattern Observer dans TanStack Query

LeDevNoviceLeDevNovice
4 min read

TanStack Query utilise le pattern Observer pour connecter la logique interne de cache avec les composants de l’interface qui consomment les données.

Son idée centrale est d’instaurer une relation un à plusieurs entre objets. Cette notion de un à plusieurs est importante à garder à l’esprit. Lorsqu’un objet change d’état (on l’appelle généralement le sujet), il notifie automatiquement tous les autres objets qui en dépendent (appelés généralement observateurs ou observer) afin qu’ils se mettent à jour en conséquence. Le sujet n’a pas besoin de connaître les détails de ses observateurs, il se contente de leur envoyer des notifications.

Un exemple concret en JavaScript est le modèle des Event Listeners du DOM. Un objet central émet un événement (un clic) et plusieurs écouteurs peuvent y réagir indépendamment.

Dans TanStack Query, cela garantit que lorsqu’une donnée est mise à jour (par exemple suite à une requête réseau réussie, un échec, ou encore une invalidation), tous les composants qui utilisent cette donnée soient informés et mis à jour automatiquement. TanStack Query implémente finalement ce qu’on pourrait appeler un observateur de données de requête, plutôt que de laisser chaque composant interroger la donnée séparément.

Le cœur query-core de la lib n’a aucune connaissance de React ou Vue. Il se contente de gérer des observateurs et des sujets (Query) de manière générique. Ensuite, l’intégration avec React (ou un autre framework) se fait en branchant ces observateurs sur le système de rendu du framework. Par exemple, côté React, le hook useQuery utilise sous le capot React.useSyncExternalStore pour s’abonner aux notifications de l’observateur nommé dans le contexte de TanStack Query un QueryObserver. Ainsi, dès que le QueryObserver indique un changement, React sait qu’il doit re-rendre le composant abonné.

Chaque Query informe ses QueryObservers au moindre changement d’état. Dit autrement, la Query est le "sujet observé" et les QueryObservers sont les "observateurs" au sens du pattern. Lorsqu’un QueryObserver reçoit une notification d’une Query (via le mécanisme interne d’abonnement de la lib), il met à jour son propre état et notifie à son tour le composant d’origine que de nouvelles données ou un nouveau statut sont disponibles.

Un avantage majeur de cette architecture est qu’elle rend extrêmement simple le partage de données entre différentes parties de l’application. Si deux composants distincts ont besoin des mêmes données, TanStack Query va automatiquement les mutualiser via la même Query et leur fournir des QueryObservers distincts pointant vers cette Query unique.

Imaginons par exemple deux composants React dans une application qui ont tous les deux besoin de la liste des tâches de l’utilisateur :

ComponentA et ComponentB font chacun un appel useQuery({ queryKey: ['todos'], queryFn: fetchTodos }).

Dans un scénario classique, on pourrait craindre que les deux appels déclenchent deux requêtes réseau concurrentes identiques. Mais grâce au pattern Observer interne, il n’en est rien ! TanStack Query repère que la queryKey ['todos'] est la même, et va réutiliser ou créer une seule Query correspondante dans le cache. Les deux composants obtiendront alors un QueryObserver pointant vers cette même Query partagée. Résultat , même si ComponentA et ComponentB se montent en même temps, la requête “todos” ne partira qu’une seule fois, et son résultat sera automatiquement alimenté dans les deux composants via leurs QueryObservers respectifs. Chaque QueryObserver garantira que son composant voit l’état à jour (isLoading, puis les données une fois disponibles par exemple), tout en évitant tout doublon de travail.

Une Query correspond à un unique fetch en cours à la fois. Si un fetch est déjà en cours et qu’un nouvel QueryObserver arrive, il ne déclenchera pas un second fetch mais rejoindra le résultat du fetch en cours. TanStack Query maintient en interne un état "fetch en cours" pour chaque Query. La Query garde trace du nombre d’observateurs qui la surveillent. Tant qu’au moins un composant est abonné (QueryObserver actif), on considère la Query active. S’il n’y a aucun QueryObserver(par exemple, l’utilisateur a navigué ailleurs dans l’app et tous les composants liés à cette donnée sont démontés), la Query devient inactive. Elle reste toutefois dans le cache pour une certaine durée, au cas où un composant reviendrait demander la même data peu après. Par défaut, TanStack Query conserve les Queries inactives en cache pendant 5 minutes avant de les supprimer pour libérer la mémoire. Cela signifie que si un composant se désabonne et qu’un autre se réabonne dans cet intervalle, la même Query sera réutilisée (et potentiellement resynchronisée si entre-temps les données sont marquées stale).

Du point de vue du développeur utilisant TanStack Query, les implications de ce design pattern Observer sont très confortable. On peut appeler useQuery dans n’importe quel composant sans se soucier des autres appels ailleurs dans l’app, la librairie se charge de centraliser les requêtes identiques. On bénéficie alors automatiquement de la déduplication des requêtes réseau (une seule requête même si X composants montent en même temps) et de la synchronisation des états (tous les composants observent la même source, donc affichent simultanément les mêmes données ou indicateurs de chargement).

Grâce au pattern Observer, TanStack Query mutualise et centralise la gestion d’une requête commune à plusieurs composants. C’est un peu comme si chaque Query était un petit "service de données" unique, auquel n’importe quel composant peut s’abonner librement.

0
Subscribe to my newsletter

Read articles from LeDevNovice directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

LeDevNovice
LeDevNovice

I'm Greg, aka LeDevNovice, a very passionate developer interested by programming and web subjects. I'm convince that in the web and programming, there is always something to learn. So I would always be a novice in terms of the amount of knowledge to learn. I always be LeDevNovice.