Normalización automática y actualizaciones de datos para bibliotecas de recuperación de datos (react-query, swr, rtk-query y más)
Introducción
Motivación
Instalación
Condiciones requeridas
Normalización de matrices.
Depuración
Actuación
Integraciones
Ejemplos
normy
es una biblioteca que permite que los datos de su aplicación se normalicen automáticamente. Luego, una vez que los datos se normalizan, en muchos casos sus datos se pueden actualizar automáticamente.
El núcleo de normy
, es decir, la biblioteca @normy/core
, que no está diseñada para usarse directamente en aplicaciones, tiene una lógica interna que permite una fácil integración con sus bibliotecas de recuperación de datos favoritas. Ya existen integraciones oficiales con react-query
, swr
y RTK Query
. Si utiliza otra biblioteca de recuperación, podría plantear el problema de Github, por lo que también podría agregarse.
Para entender lo que realmente hace normy
, lo mejor es ver un ejemplo. Supongamos que usa react-query
. Entonces podrías refactorizar un código de la siguiente manera:
importar Reaccionar desde 'reaccionar'; importar { proveedor de cliente de consulta, cliente de consulta, utilizarQueryClient, } de '@tanstack/react-query'; + importar {QueryNormalizerProvider} de '@normy/react-query'; const queryClient = nuevo QueryClient(); const Libros = () => { const consultaClient = useQueryClient(); const { datos: librosDatos = [] } = useQuery(['libros'], () => Promesa.resolve([ { id: '1', nombre: 'Nombre 1', autor: { id: '1001', nombre: 'Usuario1' } }, { id: '2', nombre: 'Nombre 2', autor: { id: '1002', nombre: 'Usuario2' } }, ]), ); const { datos: libroData } = useQuery(['libro'], () => Promesa.resolve({ identificación: '1', nombre: 'Nombre 1', autor: {id: '1001', nombre: 'Usuario1'}, }), ); const updateBookNameMutación = useMutación({ mutaciónFn: () => ({ identificación: '1', nombre: 'Nombre 1 actualizado', }),- onSuccess: mutaciónData => {- queryClient.setQueryData(['libros'], datos =>- datos.map(libro =>- libro.id === mutaciónData.id ? { ...libro, . ..mutationData } : libro,- ),- );- queryClient.setQueryData(['libro'], datos =>- data.id === mutationData.id ? { ...datos, ...mutationData } : datos,- );- },}); const updateBookAuthorMutation = useMutation({ mutaciónFn: () => ({ identificación: '1', autor: {id: '1004', nombre: 'Usuario4'}, }),- onSuccess: mutaciónData => {- queryClient.setQueryData(['libros'], datos =>- datos.map(libro =>- libro.id === mutaciónData.id ? { ...libro, . ..mutationData } : libro,- ),- );- queryClient.setQueryData(['libro'], datos =>- data.id === mutationData.id ? { ...datos, ...mutationData } : datos,- );- },}); const addBookMutación = useMutación({ mutaciónFn: () => ({ identificación: '3', nombre: 'Nombre 3', autor: {id: '1003', nombre: 'Usuario3'}, }), // con datos con matrices de nivel superior, aún necesitas actualizar los datos manualmente onSuccess: datos de mutación => { queryClient.setQueryData(['libros'], datos => data.concat(mutationData)); }, }); // devuelve algo de JSX }; const Aplicación = () => (+ <QueryNormalizerProvider queryClient={queryClient}> <QueryClientProvider cliente={queryClient}> <Libros /> </QueryClientProvider>+ </QueryNormalizerProvider> );
Entonces, como puede ver, aparte de las matrices de nivel superior, ya no son necesarias actualizaciones de datos manuales. Esto es especialmente útil si una mutación determinada debe actualizar los datos para múltiples consultas. No solo es detallado realizar actualizaciones manualmente, sino que también necesita saber exactamente qué consultas actualizar. Cuantas más consultas tengas, mayores ventajas aporta normy
.
¿Cómo funciona? De forma predeterminada, todos los objetos con clave id
están organizados por sus identificaciones. Ahora, cualquier objeto con id
de clave se normalizará, lo que simplemente significa almacenado por ID. Si ya existe un objeto coincidente con la misma identificación, uno nuevo se fusionará profundamente con el que ya se encuentra en el estado. Entonces, si los datos de respuesta de un servidor de una mutación son { id: '1', title: 'new title' }
, esta biblioteca lo resolverá automáticamente para actualizar title
del objeto con id: '1'
para todas las consultas dependientes.
También funciona con objetos anidados con identificadores, sin importar cuán profundos sean. Si un objeto con ID tiene otros objetos con ID, estos se normalizarán por separado y el objeto principal solo tendrá referencia a esos objetos anidados.
Para instalar el paquete, simplemente ejecute:
$ npm install @normy/react-query
o simplemente puede usar CDN: https://unpkg.com/@normy/react-query
.
Para instalar el paquete, simplemente ejecute:
$ npm install @normy/swr
o simplemente puede usar CDN: https://unpkg.com/@normy/swr
.
Para instalar el paquete, simplemente ejecute:
$ npm install @normy/rtk-query
o simplemente puede usar CDN: https://unpkg.com/@normy/rtk-query
.
Si desea escribir un complemento en otra biblioteca que no sea react-query
, swr
o rtk-query
:
$ npm install @normy/core
o simplemente puede usar CDN: https://unpkg.com/@normy/core
.
Para ver cómo escribir un complemento, por ahora simplemente consulte el código fuente de @normy/react-query
, es muy fácil de hacer, en el futuro se creará una guía.
Para que la normalización automática funcione, se deben cumplir las siguientes condiciones:
debe tener una forma estandarizada de identificar sus objetos, generalmente esto se hace mediante id
de clave
Los identificadores deben ser únicos en toda la aplicación, no solo en todos los tipos de objetos; de lo contrario, deberá agregarles algo; lo mismo debe hacerse en el mundo GraphQL, generalmente agregando _typename
los objetos con los mismos identificadores deben tener una estructura consistente, si un objeto como un libro en una consulta tiene una clave title
, en otras debería ser title
, no name
de repente
Hay una función que se puede pasar a createQueryNormalizer
para cumplir con esos requisitos, a saber, getNormalizationObjectKey
.
getNormalizationObjectKey
puede ayudarlo con el primer punto, si, por ejemplo, identifica los objetos de manera diferente, como por la clave _id
, entonces puede pasar getNormalizationObjectKey: obj => obj._id
.
getNormalizationObjectKey
también le permite pasar el segundo requisito. Por ejemplo, si sus identificadores son únicos, pero no en toda la aplicación, sino dentro de los tipos de objetos, podría usar getNormalizationObjectKey: obj => obj.id && obj.type ? obj.id + obj.type : undefined
o algo similar. Si eso no es posible, entonces podrías calcular un sufijo tú mismo, por ejemplo:
const getType = obj => { if (obj.bookTitle) {return 'libro'; } if (obj.apellido) {return 'usuario'; } devolver indefinido;};createQueryNormalizer(queryClient, { getNormalizationObjectKey: obj =>obj.id && getType(obj) && obj.id + getType(obj),});
El punto 3 siempre debe cumplirse; de lo contrario, debería pedirles a sus desarrolladores backend que mantengan las cosas estandarizadas y consistentes. Como último recurso, puede modificar las respuestas de su lado.
Desafortunadamente, eso no significa que nunca más necesitarás actualizar los datos manualmente. Algunas actualizaciones aún deben realizarse manualmente, como suele ocurrir, es decir, agregar y eliminar elementos de la matriz. ¿Por qué? Imagine una mutación REMOVE_BOOK
. Este libro puede estar presente en muchas consultas, la biblioteca no puede saber de qué consultas le gustaría eliminarlo. Lo mismo se aplica a ADD_BOOK
, la biblioteca no puede saber a qué consulta se debe agregar un libro, ni siquiera qué índice de matriz. Lo mismo ocurre con acciones como SORT_BOOKS
. Sin embargo, este problema afecta sólo a las matrices de nivel superior. Por ejemplo, si tiene un libro con alguna identificación y otra clave como likedByUsers
, si devuelve un libro nuevo con una lista actualizada en likedByUsers
, esto volverá a funcionar automáticamente.
Sin embargo, en la versión futura de la biblioteca, con algunos consejos adicionales, ¡también será posible realizar las actualizaciones anteriores!
Si está interesado en saber qué manipulaciones de datos realiza realmente normy
, puede utilizar la opción devLogging
:
<QueryNormalizerProvider consultaCliente={consultaCliente} normalizerConfig={{ devLogging: verdadero }}> {niños}</QueryNormalizerProvider>
false
de forma predeterminada, si se establece en true
, podrá ver en la información de la consola cuándo se configuran o eliminan las consultas.
Tenga en cuenta que esto solo funciona en desarrollo, incluso si pasa true
, no se realizará ningún registro en producción (cuando precisamente process.env.NODE_ENV === 'production'
). NODE_ENV
generalmente lo configuran paquetes de módulos como webpack
, por lo que probablemente no necesite preocuparse por configurar NODE_ENV
usted mismo.
Como siempre, cualquier automatización tiene un coste. En el futuro se podrían agregar algunos puntos de referencia, pero por ahora las pruebas manuales mostraron que, a menos que en sus datos tenga decenas de miles de objetos normalizados, la sobrecarga no debería ser notable. Sin embargo, dispone de varias formas flexibles de mejorar el rendimiento:
Puede normalizar solo consultas que tengan actualizaciones de datos y solo mutaciones que deberían actualizar los datos; eso es todo, solo puede normalizar una parte de sus datos. Consulte la documentación de integración sobre cómo hacerlo.
Como 1.
, pero para consultas y mutaciones con datos extremadamente grandes.
Hay una optimización incorporada, que verifica los datos de las respuestas a las mutaciones si en realidad son diferentes de los datos del almacén normalizado. Si es el mismo, las consultas dependientes no se actualizarán. Por lo tanto, es bueno que los datos de mutación incluyan solo cosas que en realidad podrían ser diferentes, lo que podría evitar normalizaciones y actualizaciones de consultas innecesarias.
No deshabilite la opción structuralSharing
en las bibliotecas que la admiten: si los datos de una consulta después de la actualización son referencialmente los mismos que antes de la actualización, esta consulta no se normalizará. Esta es una gran optimización del rendimiento, especialmente después de la recuperación al reenfocar, lo que podría actualizar varias consultas al mismo tiempo, generalmente con los mismos datos.
Puede utilizar la función getNormalizationObjectKey
para establecer globalmente qué objetos deberían normalizarse realmente. Por ejemplo:
<QueryNormalizerProvider consultaCliente={consultaCliente} normalizerConfig={{getNormalizationObjectKey: obj => (obj.normalizable? obj.id: indefinido), }}> {niños}</QueryNormalizerProvider>
Además, en el futuro se añadirán algunas opciones adicionales específicas de rendimiento.
Actualmente existen tres integraciones oficiales con bibliotecas de recuperación de datos, a saber, react-query
, swr
y rtk-query
. Consulte la documentación dedicada para integraciones específicas:
consulta de reacción
swr
consulta rtk
Recomiendo encarecidamente probar ejemplos de cómo se podría utilizar este paquete en aplicaciones reales.
Actualmente existen los siguientes ejemplos:
consulta de reacción
trpc
swr
consulta rtk
MIT