Elementor Query 包(@elementor/query):基于 TanStack Query 的编辑器数据请求封装与版本演进全解
【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor
在 Elementor 现代编辑器(Editor One)的 React 体系中,服务端状态管理统一收敛在一个轻量封装包@elementor/query上。本文以 packages/packages/libs/query/CHANGELOG.md 的版本演进为主轴,结合 包源码、README 与编辑器内的真实调用链,完整梳理该包的 API 设计、单例 QueryClient 管理机制、默认请求策略,以及如何在业务代码中落地分页、缓存失效与数据更新。读完本文,你将掌握 Elementor 内部服务端状态的标准写法和从版本记录反推演进脉络的方法。
一、从 CHANGELOG 看包的整体定位
@elementor/query是一个围绕@tanstack/react-query的薄封装(wrapper),目标是把 TanStack Query 的能力以受控的方式暴露给 Elementor 编辑器。它的完整版本历史记录在 packages/packages/libs/query/CHANGELOG.md,采用 Conventional Commits 规范维护,日志按时间倒序排列。
按时间线梳理版本演进:
| 版本 | 日期 | 关键变更 |
|---|---|---|
| 0.1.0 | 2023-06-28 | 首次发布:query包新增对 React Query 的支持(issue [ED-11193],PR #62) |
| 0.1.1 ~ 0.1.6 | 2023-06 ~ 2023-11 | 仅版本号 bump(Version bump only),无功能性变更 |
| 0.2.0 | 2024-01-29 | 新特性:为editor-site-navigation的 pages 面板引入分页(pagination)能力(PR #154) |
| 0.2.1 ~ 0.2.2 | 2024-07 ~ 2024-08 | 版本号 bump |
| 0.2.3 | — | 修复package.json的exports字段(Fix package.json exports field) |
| 0.2.4 | — | 更新依赖(Update dependencies) |
CHANGELOG 揭示出两个事实:其一,该包是一个平台底座型依赖,多数版本只是跟随上游更新;其二,真正影响业务的是 0.2.0 引入的分页支持和 0.2.3 对exports字段的修复——后者直接关系到包在 ESM/CJS 双格式下的导入稳定性。
二、核心 API:源码级解读
包的实现非常精简,全部逻辑集中在 packages/packages/libs/query/src/index.ts 一个文件中:
import { QueryClient } from '@tanstack/react-query'; export { useQuery, useInfiniteQuery, useMutation, useIsMutating, useQueryClient, QueryClient, QueryClientProvider, type UseQueryResult, } from '@tanstack/react-query'; let queryClient: QueryClient | undefined; export function getQueryClient(): QueryClient { if ( ! queryClient ) { throw new Error( 'Query client is not created yet.' ); } return queryClient; } export function createQueryClient() { if ( queryClient ) { throw new Error( 'Query client is already created.' ); } queryClient = new QueryClient( { defaultOptions: { queries: { refetchOnWindowFocus: false, refetchOnReconnect: false, }, }, } ); return queryClient; }2.1 全量复出口:Hooks 与类型
包直接从@tanstack/react-query复出(re-export)了业务层最常用的 API:
useQuery/useInfiniteQuery:查询数据,后者支持分页游标;useMutation:执行写入型操作(创建、更新、删除);useIsMutating:观察当前是否有进行中的 mutation;useQueryClient:在组件树中获取当前QueryClient实例;QueryClient/QueryClientProvider:客户端实例类型与 Provider 组件;type UseQueryResult:查询返回值的类型。
业务代码只需从@elementor/query导入,而不必直接依赖@tanstack/react-query,从而将第三方库锁定在包的内部,便于统一升级与替换。
2.2 单例 QueryClient:create 与 get 的成对约束
模块级变量let queryClient维护了一个进程级单例:
createQueryClient()首次创建实例;若已存在则抛出'Query client is already created.',保证整个编辑器只存在一个客户端;getQueryClient()返回已创建的实例;若尚未创建则抛出'Query client is not created yet.',用于在 React 组件树之外(如纯工具函数、事件回调)安全访问客户端。
这种“创建一次、全局复用”的设计与 React 应用中每个 Provider 各自 new 一个 Client 的常见写法不同,目的是在编辑器这种长期存活的 SPA 环境中统一缓存状态、避免多实例导致的缓存数据分叉。
2.3 默认策略:关闭窗口聚焦与断线重连时的自动刷新
createQueryClient()通过defaultOptions.queries为所有查询设置了两个全局默认值:
refetchOnWindowFocus: false:窗口重新获得焦点时不自动重新请求;refetchOnReconnect: false:网络恢复时不自动重新请求。
从源码结构看,这是针对编辑器场景的刻意取舍:编辑器面板数据以用户主动操作为主,关闭自动刷新可避免拖拽、输入过程中因窗口焦点变化引发意外请求。业务层仍可通过useQuery的局部选项覆盖这些默认值,例如 use-user.ts 中显式设置了staleTime: 30 * 60 * 1000,让用户信息在 30 分钟内直接命中缓存。
三、使用指南:从 README 示例到完整可运行代码
README 给出了最简用法:创建客户端 → 用QueryClientProvider注入 → 在组件中调用useQuery。下面将其扩写为完整可复制的示例:
import { createQueryClient, QueryClientProvider, useQuery } from '@elementor/query'; const queryClient = createQueryClient(); const App = () => ( <QueryClientProvider client={ queryClient }> <MyComponent /> </QueryClientProvider> ); const MyComponent = () => { const { data: todos, isLoading } = useQuery( { queryKey: 'todos', queryFn: () => fetch( '/todos' ).then( ( res ) => res.json() ), } ); if ( isLoading ) { return <div>Loading...</div>; } return todos.map( ( todo ) => <div key={ todo.id }>{ todo.title }</div> ); };要点说明:
queryKey支持字符串或数组。从编辑器现有代码看,团队惯例是使用命名空间数组,例如[ 'site-navigation', 'posts', postTypeSlug ](见 use-posts.ts),便于按前缀批量失效;queryFn可以是任意返回 Promise 的函数,不限于fetch,编辑器内大量使用自定义的getRequest/getSettings/getUser等 API 封装函数;- 包声明了
peerDependencies: { "react": "^18.3.1" },使用时需确保宿主环境为 React 18.3 及以上。
3.1 内部约定:__前缀不可依赖
README 特别以警告块(> [!WARNING])声明:凡是以双下划线__开头的函数或变量均视为内部实现,可能在任何版本中无通知变更,第三方开发者不得访问或依赖。这一约定属于包 API 稳定性的“红线”,在升级依赖前应检查代码中是否引用了此类符号。
四、编辑器内的真实调用链:分页、缓存失效与写入
CHANGELOG 0.2.0 提到的“pages 面板分页”特性,其实现正落在editor-site-navigation模块中,是理解该包实战价值的最佳样本。
4.1 无限滚动分页:useInfiniteQuery
use-posts.ts 完整演示了分页数据流的四个要素:
export function usePosts( postTypeSlug: Slug ) { const query = useInfiniteQuery( { queryKey: postsQueryKey( postTypeSlug ), queryFn: ( { pageParam = 1 } ) => getRequest( postTypeSlug, pageParam ), initialPageParam: 1, getNextPageParam: ( lastPage ) => { return lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : undefined; }, } ); return { ...query, data: { posts: flattenData( query.data ), total: query.data?.pages[ 0 ]?.totalPosts ?? 0 } }; }initialPageParam: 1声明起始页码;getNextPageParam依据当前页与总页数的比较返回下一页,返回undefined时表示没有更多数据;- 最后通过
flattenData把分页的pages数组拍平为帖子列表,并暴露total总数——这是对useInfiniteQuery返回结构的一次典型适配封装。
4.2 查询 + 失效:useQuery 与 invalidateQueries 的配对
读侧与写侧通过相同的queryKey约定联动:
- use-homepage.ts 用
settingsQueryKey() => [ 'site-navigation', 'homepage' ]查询设置; - use-homepage-actions.ts 在
useMutation的onSuccess回调中调用queryClient.invalidateQueries( { queryKey } )使对应缓存失效,随后onSuccess里再以{ exact: true }精确刷新——先让旧缓存失效、再按需重取,保证界面与服务器状态一致。
4.3 编辑器启动时注入客户端
editor/src/start.tsx 展示了应用引导流程:在编辑器启动入口调用createQueryClient()创建单例,并随QueryClientProvider注入根组件树,之后所有模块共享同一缓存实例。
五、工程化与发布配置
package.json 透露了该包的工程化细节:
- 构建工具为
tsup,build脚本使用仓库根级的tsup.build.ts配置,产物同时输出dist/index.js(CJS)、dist/index.mjs(ESM)与dist/index.d.ts(类型声明); exports字段按types/import/require三路分别映射——这正是 CHANGELOG 0.2.3 修复的内容,修复后 Node 与打包器在 ESM/CJS 混用场景下不再解析失败;- 当前版本号 4.4.0,包已发布到 npm(
private: false,publishConfig.access: "public"),许可为 GPL-3.0-or-later。
六、总结与升级建议
综合 CHANGELOG、源码与消费方代码,可以得到三个结论:
@elementor/query是 Elementor 编辑器的服务端状态底座,设计哲学是“薄封装 + 单例客户端 + 保守默认策略”,业务复杂度由各模块自行通过 Hook 适配承担;- 版本日志中的 0.2.0(分页)与 0.2.3(exports 修复)是两次实质性变更,其余版本均为依赖跟随;
- 升级该包时应重点回归分页面板、缓存失效链路以及 ESM 导入场景,同时避免使用
__前缀的内部符号,以保证在依赖更新后代码仍可正常编译运行。
若要在自己的业务代码中复刻这套模式,建议沿用以[ '模块名', '资源名', 参数 ]组织queryKey、以onSuccess + invalidateQueries联动写读两侧、用staleTime控制缓存有效期的既有实践,这些范式在仓库的editor-site-navigation模块中均有现成实现可供参考。
【免费下载链接】elementorThe most advanced frontend drag & drop page builder. Create high-end, pixel perfect websites at record speeds. Any theme, any page, any design.项目地址: https://gitcode.com/GitHub_Trending/el/elementor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考