Normalisation automatique et mises à jour des données pour les bibliothèques de récupération de données (react-query, swr, rtk-query et plus)
Introduction
Motivation
Installation
Conditions requises
Normalisation des tableaux
Débogage
Performance
Intégrations
Exemples
normy
est une bibliothèque qui permet de normaliser automatiquement les données de votre application. Ensuite, une fois les données normalisées, dans de nombreux cas, vos données peuvent être mises à jour automatiquement.
Le cœur de normy
- à savoir la bibliothèque @normy/core
, qui n'est pas destinée à être utilisée directement dans les applications, contient une logique qui permet une intégration facile avec vos bibliothèques de récupération de données préférées. Il existe déjà des intégrations officielles avec react-query
, swr
et RTK Query
. Si vous utilisez une autre bibliothèque de récupération, vous pourriez soulever le problème de Github, elle pourrait donc également être ajoutée.
Afin de comprendre ce que fait réellement normy
, il est préférable de voir un exemple. Supposons que vous utilisiez react-query
. Ensuite, vous pouvez refactoriser un code de la manière suivante :
importer React depuis « react » ; importer { QueryClientProvider, Client de requête, utiliserQueryClient, } depuis '@tanstack/react-query';+ import { QueryNormalizerProvider } depuis '@normy/react-query'; const queryClient = new QueryClient(); const Livres = () => { const queryClient = useQueryClient(); const { data: booksData = [] } = useQuery(['books'], () => Promesse.resolve([ { identifiant : '1', nom : 'Nom 1', auteur : { identifiant : '1001', nom : 'Utilisateur1' } }, { identifiant : '2', nom : 'Nom 2', auteur : { identifiant : '1002', nom : 'Utilisateur2' } }, ]), ); const { data: bookData } = useQuery(['book'], () => Promesse.resolve({ identifiant : '1', nom : 'Nom 1', auteur : { identifiant : '1001', nom : 'Utilisateur1' }, }), ); const updateBookNameMutation = useMutation({ mutationFn : () => ({ identifiant : '1', nom : 'Nom 1 mis à jour', }),- onSuccess : mutationData => {- queryClient.setQueryData(['books'], data =>- data.map(book =>- book.id === mutationData.id ? { ...book, . ..mutationData } : book,- ),- );- queryClient.setQueryData(['book'], data =>- data.id === mutationData.id ? { ...data, ...mutationData } : données,- );- },}); const updateBookAuthorMutation = useMutation({ mutationFn : () => ({ identifiant : '1', auteur : { identifiant : '1004', nom : 'Utilisateur4' }, }),- onSuccess : mutationData => {- queryClient.setQueryData(['books'], data =>- data.map(book =>- book.id === mutationData.id ? { ...book, . ..mutationData } : book,- ),- );- queryClient.setQueryData(['book'], data =>- data.id === mutationData.id ? { ...data, ...mutationData } : données,- );- },}); const addBookMutation = useMutation({ mutationFn : () => ({ identifiant : '3', nom : 'Nom 3', auteur : { identifiant : '1003', nom : 'Utilisateur3' }, }), // avec des données avec des tableaux de niveau supérieur, vous devez toujours mettre à jour les données manuellement onSuccess : mutationData => { queryClient.setQueryData(['books'], data => data.concat(mutationData)); }, }); // renvoie du JSX } ; const App = () => (+ <QueryNormalizerProvider queryClient={queryClient}> <QueryClientProvider client={queryClient}> <Livres /> </QueryClientProvider>+ </QueryNormalizerProvider> );
Ainsi, comme vous pouvez le constater, hormis les tableaux de niveau supérieur, aucune mise à jour manuelle des données n'est plus nécessaire. Ceci est particulièrement pratique si une mutation donnée doit mettre à jour les données de plusieurs requêtes. Non seulement cela est détaillé pour effectuer des mises à jour manuellement, mais vous devez également savoir exactement quelles requêtes mettre à jour. Plus vous avez de requêtes, plus les avantages normy
sont importants.
Comment ça marche ? Par défaut, tous les objets avec une clé id
sont organisés par leurs identifiants. Désormais, tout objet avec id
de clé sera normalisé, ce qui signifie simplement stocké par identifiant. S'il existe déjà un objet correspondant avec le même identifiant, un nouveau sera profondément fusionné avec celui déjà dans l'état. Ainsi, si les données de réponse du serveur provenant d'une mutation sont { id: '1', title: 'new title' }
, cette bibliothèque le déterminera automatiquement pour mettre à jour title
de l'objet avec id: '1'
pour toutes les requêtes dépendantes.
Il fonctionne également avec des objets imbriqués avec des identifiants, quelle que soit leur profondeur. Si un objet avec un identifiant a d'autres objets avec un identifiant, alors ceux-ci seront normalisés séparément et l'objet parent aura simplement une référence à ces objets imbriqués.
Pour installer le package, exécutez simplement :
$ npm install @normy/react-query
ou vous pouvez simplement utiliser CDN : https://unpkg.com/@normy/react-query
.
Pour installer le package, exécutez simplement :
$ npm install @normy/swr
ou vous pouvez simplement utiliser CDN : https://unpkg.com/@normy/swr
.
Pour installer le package, exécutez simplement :
$ npm install @normy/rtk-query
ou vous pouvez simplement utiliser CDN : https://unpkg.com/@normy/rtk-query
.
Si vous souhaitez écrire un plugin dans une autre bibliothèque que react-query
, swr
ou rtk-query
:
$ npm install @normy/core
ou vous pouvez simplement utiliser CDN : https://unpkg.com/@normy/core
.
Pour voir comment écrire un plugin, pour l'instant il suffit de vérifier le code source de @normy/react-query
, c'est très simple à faire, dans le futur un guide sera créé.
Pour que la normalisation automatique fonctionne, les conditions suivantes doivent être remplies :
vous devez disposer d'un moyen standardisé pour identifier vos objets, cela se fait généralement par id
de clé
les identifiants doivent être uniques dans toute l'application, pas seulement entre les types d'objets, sinon, vous devrez leur ajouter quelque chose, la même chose doit être faite dans le monde GraphQL, en ajoutant généralement _typename
les objets avec les mêmes identifiants doivent avoir une structure cohérente, si un objet comme un livre dans une requête a une clé title
, il doit être title
dans les autres, pas name
d'un coup
Il existe une fonction qui peut être transmise à createQueryNormalizer
pour répondre à ces exigences, à savoir getNormalizationObjectKey
.
getNormalizationObjectKey
peut vous aider avec le premier point, si par exemple vous identifiez les objets différemment, comme par la clé _id
, alors vous pouvez transmettre getNormalizationObjectKey: obj => obj._id
.
getNormalizationObjectKey
vous permet également de satisfaire à la 2ème exigence. Par exemple, si vos identifiants sont uniques, mais pas dans l'ensemble de l'application, mais au sein de types d'objets, vous pouvez utiliser getNormalizationObjectKey: obj => obj.id && obj.type ? obj.id + obj.type : undefined
ou quelque chose de similaire. Si cela n'est pas possible, vous pouvez simplement calculer vous-même un suffixe, par exemple :
const getType = obj => { if (obj.bookTitle) {return 'book'; } if (obj.nom) {return 'user'; } return undefined;};createQueryNormalizer(queryClient, { getNormalizationObjectKey : obj =>obj.id && getType(obj) && obj.id + getType(obj),});
Le point 3 doit toujours être respecté, sinon vous devriez vraiment demander à vos développeurs backend de garder les choses standardisées et cohérentes. En dernier recours, vous pouvez modifier les réponses de votre côté.
Malheureusement, cela ne signifie pas que vous n’aurez plus jamais besoin de mettre à jour les données manuellement. Certaines mises à jour doivent encore être effectuées manuellement, comme d'habitude, à savoir l'ajout et la suppression d'éléments du tableau. Pourquoi? Imaginez une mutation REMOVE_BOOK
. Ce livre peut être présent dans de nombreuses requêtes, la bibliothèque ne peut pas savoir de quelles requêtes vous souhaitez le supprimer. Il en va de même pour ADD_BOOK
, la bibliothèque ne peut pas savoir à quelle requête un livre doit être ajouté, ni même quel index de tableau. La même chose pour une action comme SORT_BOOKS
. Ce problème n'affecte cependant que les tableaux de niveau supérieur. Par exemple, si vous avez un livre avec un identifiant et une autre clé comme likedByUsers
, alors si vous renvoyez un nouveau livre avec une liste mise à jour dans likedByUsers
, cela fonctionnera à nouveau automatiquement.
Cependant, dans la future version de la bibliothèque, avec quelques indicateurs supplémentaires, il sera également possible d'effectuer les mises à jour ci-dessus !
Si vous êtes intéressé par les manipulations de données que fait normy
, vous pouvez utiliser l'option devLogging
:
<QueryNormalizerProvider queryClient={queryClient} normalizerConfig={{ devLogging : true }}> {enfants}</QueryNormalizerProvider>
false
par défaut, si défini sur true
, vous pouvez voir dans les informations de la console quand les requêtes sont définies ou supprimées.
Notez que cela ne fonctionne qu'en développement, même si vous passez true
, aucune journalisation ne sera effectuée en production (quand précisément process.env.NODE_ENV === 'production'
). NODE_ENV
est généralement défini pour vous par des bundlers de modules comme webpack
, vous n'avez donc probablement pas à vous soucier de la configuration NODE_ENV
vous-même.
Comme toujours, toute automatisation a un coût. À l'avenir, certains points de référence pourraient être ajoutés, mais pour l'instant, des tests manuels ont montré qu'à moins que vos données ne contiennent des dizaines de milliers d'objets normalisés, la surcharge ne devrait pas être perceptible. Cependant, vous disposez de plusieurs méthodes flexibles pour améliorer les performances :
Vous ne pouvez normaliser que les requêtes qui ont des mises à jour de données, et uniquement les mutations qui doivent mettre à jour les données - c'est tout, vous ne pouvez normaliser qu'une partie de vos données. Consultez une documentation d'intégration pour savoir comment procéder.
Comme 1.
, mais pour les requêtes et les mutations avec des données extrêmement volumineuses.
Il existe une optimisation intégrée qui vérifie les données des réponses de mutation si elles sont réellement différentes des données du magasin normalisé. Si c'est le même, les requêtes dépendantes ne seront pas mises à jour. Il est donc bon que les données de mutation incluent uniquement des éléments qui pourraient réellement être différents, ce qui pourrait éviter une normalisation et des mises à jour inutiles des requêtes.
Ne désactivez pas l'option structuralSharing
dans les bibliothèques qui la prennent en charge - si les données d'une requête après la mise à jour sont les mêmes référentiellement qu'avant la mise à jour, alors cette requête ne sera pas normalisée. Il s'agit d'une optimisation importante des performances, en particulier après une récupération lors du recentrage, qui pourrait mettre à jour plusieurs requêtes en même temps, généralement sur les mêmes données.
Vous pouvez utiliser la fonction getNormalizationObjectKey
pour définir globalement quels objets doivent être réellement normalisés. Par exemple:
<QueryNormalizerProvider queryClient={queryClient} normalizerConfig={{getNormalizationObjectKey : obj => (obj.normalizing ? obj.id : non défini), }}> {enfants}</QueryNormalizerProvider>
De plus, à l’avenir, des options supplémentaires spécifiques aux performances seront ajoutées.
Il existe actuellement trois intégrations officielles avec des bibliothèques de récupération de données, à savoir react-query
, swr
et rtk-query
. Voir les documentations dédiées pour les intégrations spécifiques :
requête de réaction
swr
requête rtk
Je recommande fortement d'essayer des exemples sur la manière dont ce package pourrait être utilisé dans des applications réelles.
Il existe actuellement les exemples suivants :
requête de réaction
trpc
swr
requête rtk
MIT