数据获取库的自动规范化和数据更新(react-query、swr、rtk-query 等)
介绍
动机
安装
所需条件
数组标准化
调试
表现
集成
示例
normy
是一个库,它允许您的应用程序数据自动标准化。然后,一旦数据标准化,在许多情况下您的数据就可以自动更新。
normy
的核心 - 即@normy/core
库,并不意味着直接在应用程序中使用,其内部逻辑允许与您最喜欢的数据获取库轻松集成。已经有与react-query
、 swr
和RTK Query
官方集成。如果您使用其他获取库,则可能会引发 Github 问题,因此也可能会添加它。
为了理解normy
实际上做了什么,最好看一个例子。假设您使用react-query
。然后你可以通过以下方式重构代码:
从“反应”导入反应; 进口 { 查询客户端提供者, 查询客户端, 使用查询客户端, } from '@tanstack/react-query';+ import { QueryNormalizerProvider } from '@normy/react-query'; const queryClient = new QueryClient(); const 书籍 = () => { const queryClient = useQueryClient(); const { data: booksData = [] } = useQuery(['books'], () => Promise.resolve([ { id: '1', name: '姓名1', 作者: { id: '1001', name: '用户1' } }, { id: '2', name: '姓名 2', 作者: { id: '1002', name: '用户2' } }, ]), ); const { data: bookData } = useQuery(['book'], () => Promise.resolve({ id: '1', 名称:'名称1', 作者: { id: '1001', name: 'User1' }, }), ); const updateBookNameMutation = useMutation({ 突变Fn:()=>({ id: '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:()=>({ id: '1', 作者: { id: '1004', name: '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:()=>({ id: '3', 名称:'名称3', 作者:{ id: '1003', name: 'User3' }, }), // 对于顶级数组的数据,仍然需要手动更新数据 onSuccess: 突变数据 => { queryClient.setQueryData(['books'], data => data.concat(mutationData)); }, }); // 返回一些 JSX }; const App = () => (+ <QueryNormalizerProvider queryClient={queryClient}> <QueryClientProvider client={queryClient}> <书籍/> </QueryClientProvider>+ </QueryNormalizerProvider> );
因此,正如您所看到的,除了顶级数组之外,不再需要手动数据更新。如果给定的突变应该更新多个查询的数据,这尤其方便。手动执行更新不仅很冗长,而且您还需要确切地知道要更新哪些查询。您的查询越多, normy
带来的优势就越大。
它是如何运作的?默认情况下,所有具有id
键的对象都按其 id 进行组织。现在,任何具有键id
的对象都将被标准化,这简单地意味着通过 id 存储。如果已经存在具有相同 id 的匹配对象,则新的对象将与状态中已有的对象深度合并。因此,如果来自突变的服务器响应数据是{ id: '1', title: 'new title' }
,则该库将自动计算出更新所有相关查询的id: '1'
对象的title
。
它也适用于带有 id 的嵌套对象,无论多深。如果一个具有 id 的对象有其他具有 id 的对象,那么这些对象将被单独规范化,并且父对象将仅引用这些嵌套对象。
要安装该软件包,只需运行:
$ npm install @normy/react-query
或者您可以只使用 CDN: https://unpkg.com/@normy/react-query
://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
://unpkg.com/@normy/rtk-query 。
如果你想编写一个插件到除了react-query
、 swr
或rtk-query
之外的另一个库:
$ npm install @normy/core
或者您可以只使用 CDN: https://unpkg.com/@normy/core
。
要了解如何编写插件,现在只需查看@normy/react-query
的源代码,这很容易做到,将来会创建一个指南。
为了使自动归一化工作,必须满足以下条件:
你必须有一个标准化的方法来识别你的对象,通常这是通过密钥id
来完成的
id 在整个应用程序中必须是唯一的,而不仅仅是在对象类型之间,如果不是,您将需要向它们附加一些内容,在 GraphQL 世界中也必须这样做,通常添加_typename
具有相同 id 的对象应该具有一致的结构,如果一个查询中像书这样的对象具有title
键,那么它在其他查询中应该是title
,而不是突然出现的name
有一个函数可以传递给createQueryNormalizer
来满足这些要求,即getNormalizationObjectKey
。
getNormalizationObjectKey
可以帮助您解决第一点,例如,如果您以不同的方式标识对象,例如通过_id
键,那么您可以传递getNormalizationObjectKey: obj => obj._id
。
getNormalizationObjectKey
还允许您通过第二个要求。例如,如果您的 id 是唯一的,但不是在整个应用程序中唯一,而是在对象类型内,则可以使用getNormalizationObjectKey: obj => obj.id && obj.type ? obj.id + obj.type : undefined
或类似的东西。如果这是不可能的,那么您可以自己计算后缀,例如:
const getType = obj => { if (obj.bookTitle) {return '书'; } if (obj.surname) {return '用户'; } 返回未定义;};createQueryNormalizer(queryClient, { getNormalizationObjectKey: obj =>obj.id && getType(obj) && obj.id + getType(obj),});
第 3 点应该始终得到满足,如果没有,您真的应该要求后端开发人员保持标准化和一致性。作为最后的手段,您可以修改自己的回复。
不幸的是,这并不意味着您永远不需要再手动更新数据。一些更新仍然需要像平常一样手动完成,即从数组中添加和删除项目。为什么?想象一下REMOVE_BOOK
突变。这本书可能出现在许多查询中,图书馆无法知道您想从哪些查询中删除它。这同样适用于ADD_BOOK
,图书馆不知道应该将一本书添加到哪个查询,甚至不知道哪个数组索引。 SORT_BOOKS
之类的操作也是如此。不过,这个问题仅影响顶级数组。例如,如果您有一本书,其中包含某个 id 和另一个键(如likedByUsers
),那么如果您返回带有更新列表的新书likedByUsers
,这将自动再次起作用。
不过,在该库的未来版本中,通过一些额外的指示,也可以进行上述更新!
如果您对数据操作normy
实际上做了什么感兴趣,您可以使用devLogging
选项:
<查询规范化器提供者 查询客户端={查询客户端} NormalizerConfig={{ devLogging: true }}> {children}</QueryNormalizerProvider>
默认为false
,如果设置为true
,您可以在设置或删除查询时在控制台信息中看到。
请注意,这仅在开发中有效,即使您传递true
,在生产中也不会进行任何日志记录(当恰好process.env.NODE_ENV === 'production'
时)。 NODE_ENV
通常由webpack
等模块捆绑程序为您设置,因此您可能不需要担心自己设置NODE_ENV
。
一如既往,任何自动化都会带来成本。将来可以添加一些基准,但目前手动测试表明,除非您的数据中有数以万计的标准化对象,否则开销应该不明显。但是,您可以通过多种灵活的方法来提高性能:
您可以仅规范化具有数据更新的查询,以及仅应更新数据的突变 - 就是这样,您只能规范化部分数据。检查集成文档如何执行此操作。
与1.
类似,但适用于具有极大数据的查询和突变。
有一个内置的优化,它检查突变响应中的数据是否实际上与标准化存储中的数据不同。如果相同,则相关查询将不会更新。因此,突变数据最好只包含实际上可能不同的内容,这可以防止不必要的标准化和查询更新。
不要在支持它的库中禁用structuralSharing
选项 - 如果更新后的查询数据与更新前的引用相同,则该查询将不会被规范化。这是一个很大的性能优化,特别是在重新聚焦时重新获取之后,它可以同时更新多个查询,通常更新到完全相同的数据。
您可以使用getNormalizationObjectKey
函数全局设置哪些对象应该实际标准化。例如:
<查询规范化器提供者 查询客户端={查询客户端} NormalizerConfig={{getNormalizationObjectKey: obj => (obj.normalized ? obj.id : 未定义), }}> {children}</QueryNormalizerProvider>
此外,将来还会添加一些额外的特定于性能的选项。
目前官方集成了三个数据获取库,即react-query
、 swr
和rtk-query
。请参阅特定集成的专用文档:
反应查询
驻波比
RTK查询
我强烈建议尝试示例如何在实际应用程序中使用该包。
目前有以下例子:
反应查询
特尔普克
驻波比
RTK查询
麻省理工学院