Автоматическая нормализация и обновление данных для библиотек выборки данных (react-query, swr, rtk-query и т. д.)
Введение
Мотивация
Установка
Требуемые условия
Нормализация массивов
Отладка
Производительность
Интеграции
Примеры
normy
— это библиотека, которая позволяет автоматически нормализовать данные вашего приложения. Затем, как только данные будут нормализованы, во многих случаях ваши данные могут быть обновлены автоматически.
Ядро normy
, а именно библиотека @normy/core
, которая не предназначена для непосредственного использования в приложениях, имеет внутри логику, позволяющую легко интегрироваться с вашими любимыми библиотеками выборки данных. Уже есть официальные интеграции с react-query
, swr
и RTK Query
. Если вы используете другую библиотеку выборки, вы можете поднять проблему с Github, поэтому ее тоже можно добавить.
Чтобы понять, что на самом деле делает normy
, лучше всего посмотреть пример. Предположим, вы используете react-query
. Затем вы можете реорганизовать код следующим образом:
импортировать React из «реагировать»; импортировать { QueryClientProvider, ЗапросКлиент, использоватьQueryClient, } из '@tanstack/react-query';+ import { QueryNormalizerProvider } из '@normy/react-query'; const queryClient = новый QueryClient(); const Книги = () => { const queryClient = useQueryClient(); const { data: bookData = [] } = useQuery(['books'], () => Обещание.решить([ { id: '1', name: 'Name 1', автор: { id: '1001', name: 'User1' } }, { id: '2', name: 'Name 2', автор: { id: '1002', name: 'User2' } }, ]), ); const { data: bookData } = useQuery(['book'], () => Обещание.resolve({ идентификатор: '1', имя: 'Имя 1', автор: { id: '1001', имя: 'User1' }, }), ); const updateBookNameMutation = useMutation({ мутацияFn: () => ({ идентификатор: '1', name: 'Имя 1 обновлено', }),- 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 } : данные,- );- },}); const updateBookAuthorMutation = useMutation({ мутацияFn: () => ({ идентификатор: '1', автор: { id: '1004', имя: 'User4' }, }),- 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 } : данные,- );- },}); const addBookMutation = useMutation({ мутацияFn: () => ({ идентификатор: '3', имя: 'Имя 3', автор: { id: '1003', имя: 'User3' }, }), // с данными с массивами верхнего уровня все равно придется обновлять данные вручную onSuccess:mutationData => { queryClient.setQueryData(['books'], data => data.concat(mutationData)); }, }); // возвращаем немного JSX }; const App = () => (+ <QueryNormalizerProvider queryClient={queryClient}> <QueryClientProvider client={queryClient}> <Книги /> </QueryClientProvider>+ </QueryNormalizerProvider> );
Итак, как видите, кроме массивов верхнего уровня, никаких ручных обновлений данных больше не требуется. Это особенно удобно, если данная мутация должна обновлять данные для нескольких запросов. Мало того, что обновлять вручную вручную сложно, но еще и нужно точно знать, какие запросы обновлять. Чем больше у вас запросов, тем больше преимуществ дает normy
.
Как это работает? По умолчанию все объекты с ключом id
организованы по их идентификаторам. Теперь любой объект с id
ключа будет нормализован, что просто означает сохранение по идентификатору. Если соответствующий объект с таким же идентификатором уже существует, новый будет глубоко объединен с объектом, уже находящимся в состоянии. Таким образом, если данные ответа сервера от мутации равны { id: '1', title: 'new title' }
, эта библиотека автоматически выяснит это и обновит title
для объекта с id: '1'
для всех зависимых запросов.
Он также работает с вложенными объектами с идентификаторами, независимо от их глубины. Если у объекта с идентификатором есть другие объекты с идентификаторами, то они будут нормализованы отдельно, а родительский объект будет иметь только ссылку на эти вложенные объекты.
Чтобы установить пакет, просто запустите:
$ npm install @normy/react-query
или вы можете просто использовать CDN: https://unpkg.com/@normy/react-query
.
Чтобы установить пакет, просто запустите:
$ npm install @normy/swr
или вы можете просто использовать CDN: https://unpkg.com/@normy/swr
.
Чтобы установить пакет, просто запустите:
$ npm install @normy/rtk-query
или вы можете просто использовать CDN: https://unpkg.com/@normy/rtk-query
.
Если вы хотите написать плагин для другой библиотеки, кроме react-query
, swr
или rtk-query
:
$ npm install @normy/core
или вы можете просто использовать CDN: https://unpkg.com/@normy/core
.
Чтобы узнать, как написать плагин, просто проверьте исходный код @normy/react-query
, это очень легко сделать, в будущем будет создано руководство.
Для того, чтобы автоматическая нормализация работала, должны быть выполнены следующие условия:
у вас должен быть стандартизированный способ идентификации ваших объектов, обычно это делается по id
ключа
идентификаторы должны быть уникальными во всем приложении, а не только между типами объектов; в противном случае вам нужно будет что-то добавить к ним, то же самое нужно сделать в мире GraphQL, обычно добавляя _typename
объекты с одинаковыми идентификаторами должны иметь согласованную структуру: если объект, такой как книга, в одном запросе имеет ключ title
, в других он должен быть title
, а не name
внезапно
Существует функция, которую можно передать в createQueryNormalizer
для удовлетворения этих требований, а именно getNormalizationObjectKey
.
getNormalizationObjectKey
может помочь вам с первым пунктом: если, например, вы идентифицируете объекты по-другому, например, по ключу _id
, тогда вы можете передать getNormalizationObjectKey: obj => obj._id
.
getNormalizationObjectKey
также позволяет выполнить второе требование. Например, если ваши идентификаторы уникальны, но не во всем приложении, а внутри типов объектов, вы можете использовать getNormalizationObjectKey: obj => obj.id && obj.type ? obj.id + obj.type : undefined
или что-то подобное. Если это невозможно, вы можете просто вычислить суффикс самостоятельно, например:
const getType = obj => { if (obj.bookTitle) {вернуть 'книгу'; } if (obj.surname) {return 'user'; } вернуть неопределенное;};createQueryNormalizer(queryClient, { getNormalizationObjectKey: obj =>obj.id && getType(obj) && obj.id + getType(obj),});
Пункт 3 всегда должен выполняться, в противном случае вам действительно следует попросить своих серверных разработчиков поддерживать стандартизацию и последовательность. В крайнем случае, вы можете внести изменения в ответы со своей стороны.
К сожалению, это не означает, что вам больше никогда не придется обновлять данные вручную. Некоторые обновления по-прежнему необходимо выполнять вручную, как обычно, а именно: добавлять и удалять элементы из массива. Почему? Представьте себе мутацию REMOVE_BOOK
. Эта книга может присутствовать во многих запросах, библиотека не может знать, из каких запросов вы хотите ее удалить. То же самое относится и к ADD_BOOK
: библиотека не может знать, к какому запросу должна быть добавлена книга или даже как к какому индексу массива. То же самое и с действием типа SORT_BOOKS
. Однако эта проблема затрагивает только массивы верхнего уровня. Например, если у вас есть книга с некоторым идентификатором и другим ключом, например, likedByUsers
, то если вы вернете новую книгу с обновленным списком в likedByUsers
, это снова будет работать автоматически.
Однако в будущей версии библиотеки, с некоторыми дополнительными указателями, можно будет делать и вышеуказанные обновления!
Если вам интересно, какие манипуляции с данными на самом деле выполняет normy
, вы можете использовать опцию devLogging
:
<QueryNormalizerProvider queryClient={queryClient} normalizerConfig={{ devLogging: true }}> {дети</QueryNormalizerProvider>
по умолчанию false
, если установлено значение true
, вы можете видеть в консоли информацию о том, когда запросы устанавливаются или удаляются.
Обратите внимание, что это работает только в разработке, даже если вы передадите true
, в рабочей среде ведение журнала не будет (точно в случае, когдаprocess.env.NODE_ENV process.env.NODE_ENV === 'production'
). NODE_ENV
обычно устанавливается сборщиками модулей, такими как webpack
, поэтому, вероятно, вам не нужно беспокоиться о настройке NODE_ENV
самостоятельно.
Как всегда, любая автоматизация имеет свою цену. В будущем могут быть добавлены некоторые тесты, но на данный момент ручные тесты показали, что, если в ваших данных нет десятков тысяч нормализованных объектов, накладные расходы не должны быть заметными. Однако у вас есть несколько гибких способов улучшить производительность:
Вы можете нормализовать только запросы, которые имеют обновления данных, и только мутации, которые должны обновлять данные - вот и все, вы можете нормализовать только часть ваших данных. Проверьте документацию по интеграции, как это сделать.
Как 1.
, но для запросов и мутаций с очень большими данными.
Существует встроенная оптимизация, которая проверяет данные из ответов на мутации, действительно ли они отличаются от данных в нормализованном хранилище. Если оно одинаковое, зависимые запросы не будут обновляться. Таким образом, полезно, чтобы данные о мутациях включали только те вещи, которые на самом деле могут отличаться, что может предотвратить ненужную нормализацию и обновление запросов.
Не отключайте опцию structuralSharing
в библиотеках, которые ее поддерживают — если данные запроса после обновления ссылочно такие же, как и до обновления, то этот запрос не будет нормализован. Это значительная оптимизация производительности, особенно после повторной выборки при перефокусировке, которая может одновременно обновлять несколько запросов, обычно для одних и тех же данных.
Вы можете использовать функцию getNormalizationObjectKey
, чтобы глобально установить, какие объекты должны быть фактически нормализованы. Например:
<QueryNormalizerProvider queryClient={queryClient} normalizerConfig={{getNormalizationObjectKey: obj => (obj.normalizable ? obj.id: не определено), }}> {дети</QueryNormalizerProvider>
Более того, в будущем будут добавлены некоторые дополнительные параметры, специфичные для производительности.
В настоящее время существует три официальные интеграции с библиотеками выборки данных, а именно: react-query
, swr
и rtk-query
. См. специальную документацию для конкретных интеграций:
ответный запрос
КСВ
rtk-запрос
Я настоятельно рекомендую попробовать примеры использования этого пакета в реальных приложениях.
На данный момент существуют следующие примеры:
ответный запрос
трпк
КСВ
rtk-запрос
Массачусетский технологический институт