首页/新闻资讯/正文详情

wp-calypso Reader 数据层迁移指南:从 Redux Data-Layer 到 React Query 的完整实战方案

发布时间:2026/9/23 3:33:59 来源:云帆数科 栏目:资讯中心
wp-calypso Reader 数据层迁移指南:从 Redux Data-Layer 到 React Query 的完整实战方案
前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载导读本文档对应仓库 .claude/skills/calypso-react-query-migration/SKILL.md是 wp-calypsoWordPress.com 的 JavaScript 与 API 驱动前端中Reader 数据获取代码迁移的官方工程规范。目标架构清晰fetcher 收敛到automattic/api-core→ query/mutation 选项收敛到automattic/api-queries→ 组件直接调用useQuery()/useMutation()。读完本文你将掌握如何把 Reader 的 Redux>digraph migration_flow { Migration request [shapedoublecircle]; Audit CRUD existing fetchers [shapebox]; Write plan [shapebox]; Bridge decision [shapediamond]; Plan: remove bridge adapt consumers [shapebox]; Plan: HOC for class component [shapebox]; Plan: keep bridge (record reason) [shapebox]; Execute plan with checkpoints [shapebox]; Migration request - Audit CRUD existing fetchers; Audit CRUD existing fetchers - Bridge decision; Bridge decision - Plan: remove bridge adapt consumers [labelfew function-component consumers]; Bridge decision - Plan: HOC for class component [labelclass component is main consumer]; Bridge decision - Plan: keep bridge (record reason) [labelmany/cross-cutting consumers]; Plan: remove bridge adapt consumers - Write plan; Plan: HOC for class component - Write plan; Plan: keep bridge (record reason) - Write plan; Write plan - Execute plan with checkpoints; }写代码之前必须先做计划。一行式需求如 migrate QueryReaderTag只是规格说明必须先转化为计划。必需的子技能superpowers:writing-plans—— 起草计划superpowers:executing-plans—— 带检查点执行计划计划必须包含完整 CRUD 审计、桥接决策附理由、提交拆分、每个 mutation 的失效策略 乐观更新决策、待更新的消费者清单、测试计划。计划保存到.context/plan-migrate-reader-{name}.md。四、Step 1动手前先审计4.1 CRUD 覆盖审计列出该资源的所有 action——读操作和写操作都要列。部分迁移会制造双真相例如已删除的条目在刷新前仍显示在侧边栏grep -E READER_[A-Z_]* client/state/data-layer/wpcom/read/{name}/index.js grep -E READER_[A-Z_]* client/state/reader/{name}/actions.ts操作Redux 模式迁移目标读READER_XXX_REQUEST→dispatchRequestGETuseQuery(readXxxQuery(...))增 / 改 / 删READER_XXX_*→dispatchRequestPOSTuseMutation(...Mutation())Follow / UnfollowREADER_XXX_FOLLOW等各自独立的 mutation每个 action 组单独一个 commit每个 commit 同时移除其 Redux 对应物mutation 不需要桥接组件。4.2 已有api-corefetcher不要重复造轮子。创建前先 grepgrep -rn /read/{endpoint} packages/api-core/src/ ls packages/api-core/src/ | grep -i {name}如果 fetcher 已存在复用从automattic/api-coreimport或扩展加入既有文件夹而非另建平行的新文件夹。4.3 缓存键消费者强制检查在修改或替换 query key 之前先找到所有读取/写入该 key 的其他代码。mutation 和共享 helper 经常针对旧 key 做乐观setQueryData/invalidateQueries——只改 key 不更新它们会导致这些调用静默 no-op用户每次操作后看到的都是过期数据。# 找到旧 key 的每一处引用。 grep -rn [\]{old-key-segment}[\] packages/ client/ --include*.ts --include*.tsx # 同时检查共享 helper——它们常集中承载乐观逻辑。 grep -rn alter{Name}\|invalidate{Name} packages/data-stores/src/reader/helpers/对每个匹配结果做出决策更新为新 key首选——同一 PR 内附带小补丁或随同迁移的 mutation 一起改一并迁移该消费者当它是本就该加入本次迁移的 mutation 时桥接最后手段让旧 key 并行存活直到消费者迁移完成。每个匹配的决策都要记录在计划里。陷阱当旧 key 的唯一消费者是某个 mutation 的乐观写入时删除># 渲染位置 grep -rn QueryReader{Name} client/ --include*.{ts,tsx,js,jsx} # 间接的 Redux 消费者 grep -rn get{Name}\|isRequesting{Name} client/ --include*.{ts,tsx,js,jsx}审计结果推荐决策1–3 个函数组件消费者slice 未被其他处读取移除桥接。消费者改为useQuery。删除RECEIVEaction、reducer、selectors。类组件是主要消费者用 HOC 包裹类组件HOC 内调用useQuery()并把结果以 props 转发。删除桥接 slice。HOC 模式见 redux-cleanup.md消费者很多、或 selector 被跨代码库使用、或跨 Reader 代码读取 slice保留桥接。在计划中记录理由标注为后续工作。决策要逐字记录进计划。移除桥接前先 grep 确认没有其他代码在读取RECEIVE/ reducer / selectors。六、Step 3构建迁移6.1 创建 fetcherapi-core// packages/api-core/src/read-{name}/fetchers.ts import { addQueryArgs } from wordpress/url; import { wpcom } from ../wpcom-fetcher; import type { ReadXxxResponse } from ./types; export const fetchReadXxx ( params?: SomeParams ): Promise ReadXxxResponse { return wpcom.req.get( { path: addQueryArgs( /read/endpoint, { key: value } ), apiVersion: 1.2, } ); };从旧>const getSubkey (): string | undefined ( window as typeof window { currentUser?: { subscriptionManagementSubkey?: string } } ) .currentUser?.subscriptionManagementSubkey; export const fetchReadXxx async ( params ): Promise ReadXxxResponse string { const path /read/...; const subkey getSubkey(); if ( subkey ) { const response await fetch( https://public-api.wordpress.com/wpcom/v2${ path }, { method: GET, credentials: same-origin, headers: { Authorization: X-WPSUBKEY ${ encodeURIComponent( subkey ) }, Content-Type: application/json, }, } ); return response.json(); } return wpcom.req.get( { path, apiNamespace: wpcom/v2, apiVersion: 2 } ); };使用原生fetch而非wordpress/api-fetch——既避免给api-core增加依赖subkey 路径也不需要apiFetch的任何中间件。client/lib/request-with-subkey-fallback/这个 helper 对非 Reader 代码做同样的事但不要从api-coreimport 它依赖方向错误把逻辑内联进来。6.3 创建 query 选项api-queries// packages/api-queries/src/read-{name}.ts import { fetchReadXxx } from automattic/api-core; import { queryOptions } from tanstack/react-query; export const readXxxQuery ( param?: string | null ) queryOptions( { queryKey: [ read, xxx, param ], staleTime: 1000 * 60 * 5, queryFn: () fetchReadXxx( param! ), enabled: param ! null, } );queryKey[read, {domain}, ...params]。staleTime数据有外部变更事件支付、续费、服务端 mutation时约 1 分钟变化缓慢的列表约 5 分钟。不确定时与后端确认变更频率。enabledparams 可能为 null 时设置。仓库中的真实实现可对照packages/api-queries/src/read-tags.ts 的readTagsQuerystaleTime: 1000 * 60 * 5queryKey[read, tags, followed, locale]与readTagQueryenabled: !! slugpackages/api-queries/src/read-feed.ts 的readFeedQueryFEED_STALE_TIME 1000 * 60、isValidFeedId校验、meta: { persist: true }与readFeedSearchQuery将查询截断至 500 字符以复刻旧 reducer 行为enabled: Boolean( query )。注意read-feed.ts还保留了旧行为边界搜索列表分页上限 200、每页 10 条与/read/feed的 Elasticsearch 固定窗口分页对齐。当从data-stores迁移时保留相同的queryKey以保证会话内缓存兼容。6.4 桥接组件仅当保留时// client/components/data/query-reader-{name}/index.tsx import { readXxxQuery } from automattic/api-queries; import { useQuery } from tanstack/react-query; import { useEffect } from react; import { useDispatch } from react-redux; import { receiveXxx } from calypso/state/reader/xxx/actions; export default function QueryReaderXxx() { const dispatch useDispatch(); const { data } useQuery( readXxxQuery() ); useEffect( () { if ( data?.items ) dispatch( receiveXxx( data.items ) ); }, [ data, dispatch ] ); return null; }如果旧代码分别 dispatch 成功/失败 action就用isSuccess/isError两个 effect 镜像两者。6.5 测试完整模板createTestStore、renderWithProviders、nock配置见 test-scaffolding.md。nock URL 规则wpcom.req.get调用 →https://public-api.wordpress.com/rest/v{apiVersion}/{path}apiNamespace: wpcom/v2的 v2 调用 →https://public-api.wordpress.com/wpcom/v2{path}。运行测试yarn test-client client/components/data/query-reader-{name}/test/测试脚手架的几个关键约定nock 的query必须与 fetcher 用addQueryArgs产出的查询参数一致测试QueryClient必须设retry: false避免失败请求循环拖垮测试用createTestStore包装 dispatch 以断言桥接组件触发了哪些 action而不把测试绑定到特定 reducer。6.6 Mutations增 / 改 / 删完整规范见 mutations.md覆盖 mutator、mutationOptions、缓存失效规则、乐观更新面向用户可见的 mutation 默认启用、副作用归属在消费者的onSuccess而非 api-queries。Mutator 示例packages/api-core/src/read-tags/mutators.ts 中的followReadTag/unfollowReadTag即此类实现// packages/api-core/src/read-{name}/mutators.ts import { wpcom } from ../wpcom-fetcher; import type { CreateXxxParams, XxxResponse } from ./types; export const createXxx ( params: CreateXxxParams ): Promise XxxResponse { return wpcom.req.post( { path: /read/xxx/new, apiVersion: 1.2, body: params, } ); }; export const updateXxx ( params: UpdateXxxParams ): Promise XxxResponse { return wpcom.req.post( { path: /read/xxx/${ params.owner }/${ params.slug }/update, apiVersion: 1.2, body: params, } ); }; export const deleteXxx ( owner: string, slug: string ): Promise void { return wpcom.req.post( { path: /read/xxx/${ owner }/${ slug }/delete, apiVersion: 1.2, body: {}, } ); };Mutation 选项与缓存失效规则export const createXxxMutation () mutationOptions( { mutationFn: createXxx, onSuccess: () { queryClient.invalidateQueries( { queryKey: readXxxListQuery().queryKey } ); }, } ); export const updateXxxMutation () mutationOptions( { mutationFn: updateXxx, onSuccess: ( data ) { queryClient.invalidateQueries( { queryKey: readXxxQuery( data.owner, data.slug ).queryKey } ); queryClient.invalidateQueries( { queryKey: readXxxListQuery().queryKey } ); }, } ); export const deleteXxxMutation () mutationOptions( { mutationFn: ( { owner, slug }: { owner: string; slug: string } ) deleteXxx( owner, slug ), onSuccess: ( _data, { owner, slug } ) { queryClient.removeQueries( { queryKey: readXxxQuery( owner, slug ).queryKey } ); queryClient.invalidateQueries( { queryKey: readXxxListQuery().queryKey } ); }, } );Create失效列表 query让新条目出现Update失效条目 query和列表 queryDelete条目 query 用removeQueries它已不存在列表 query 用invalidateQueriesFollow/Unfollow失效所有读取关注状态的 query跳过失效是最常见的 bug——UI 一直保留过期数据直到手动刷新。偏好乐观更新。Redux >export const updateXxxOptimisticMutation () mutationOptions( { mutationFn: updateXxx, onMutate: async ( newValue ) { // 取消在途 refetch避免覆盖我们的乐观写入 await queryClient.cancelQueries( { queryKey: readXxxQuery( newValue.id ).queryKey } ); // 快照旧值用于回滚 const previous queryClient.getQueryData( readXxxQuery( newValue.id ).queryKey ); // 乐观写入新值 queryClient.setQueryData( readXxxQuery( newValue.id ).queryKey, newValue ); return { previous }; }, onError: ( _err, variables, context ) { if ( context?.previous ) { queryClient.setQueryData( readXxxQuery( variables.id ).queryKey, context.previous ); } }, onSettled: ( _data, _err, variables ) { // 无论成败都 refetch与服务端状态对齐 queryClient.invalidateQueries( { queryKey: readXxxQuery( variables.id ).queryKey } ); }, } );参考实现packages/api-queries/src/me-preferences.ts中的userPreferenceOptimisticMutation第 136 行起——onMutate里cancelQueries后快照previous、setQueryData合并新偏好onError回滚。各 mutation 类型的乐观模式Create把新条目乐观推进列表缓存onSuccess中用真实 ID 替换临时 IDUpdate快照条目、写入新值、出错回滚Delete乐观从列表缓存移除条目出错恢复Follow/Unfollow乐观翻转缓存中的布尔值出错回滚何时不要用乐观更新mutation 结果依赖客户端无法预测的服务端计算数据生成的 slug、服务端分配的 ID、派生计数——改用占位 onSuccess对账或跳过乐观、接受延迟罕见/非交互 mutation后台同步、管理操作额外复杂度不值得。始终配onSettled失效让缓存最终反映服务端真相。仓库中的实战范例是 packages/api-queries/src/read-tags.ts 的followReadTagMutation/unfollowReadTagMutationonMutate里cancelFollowedTags后patchFollowedTags直接改缓存中的原始响应形状缓存存原始{ tags: ReadTag[] }select在读时归一化onError只回滚本标签以免殃及其他在途 follow/unfollow 的乐观变更onSettled统一invalidateFollowedTags。该文件还演示了两个细节mutation 工厂接收调用方的queryClient因为 Calypso 自建 QueryClient见下文以及把already_subscribed错误当作成功处理以匹配旧>import { deleteXxxMutation } from automattic/api-queries; import { useMutation } from tanstack/react-query; import page from automattic/calypso-router; import { errorNotice, successNotice } from calypso/state/notices/actions; function MyComponent( { item } ) { const dispatch useDispatch(); const translate useTranslate(); const { mutate: deleteItem, isPending } useMutation( deleteXxxMutation() ); const handleDelete () { deleteItem( { owner: item.owner, slug: item.slug }, { onSuccess: () { page( /reader ); dispatch( successNotice( translate( Deleted successfully. ) ) ); }, onError: () { dispatch( errorNotice( translate( Unable to delete. ) ) ); }, } ); }; return Button onClick{ handleDelete } disabled{ isPending } /; }副作用归属通知、导航、Redux receive action放在消费者的onSuccess/onError回调缓存失效放在mutation的onSuccessapi-queries 内这个拆分让 mutation 保持可复用同时每个消费者控制自己的 UX。同 commit 内清理 Redux。mutation 不需要桥接模式——消费者改用isPending后没有其他地方会再读 Redux 里的 isCreating/isUpdating。移除READER_XXX_CREATE/_UPDATE/_DELETEaction 类型、请求 action creatorcreateReaderList等、data-layer handler、isCreatingXxx/isUpdatingXxxreducer 与 selector。保留仍被其他组件从 Redux 读取数据的RECEIVEaction 和 reducer——从消费者的onSuccess里 dispatch 它们。测试 mutationnock( https://public-api.wordpress.com ) .post( /rest/v1.2/read/xxx/owner/slug/delete ) .reply( 200 );测试清单用正确方法 mockPOST 用nock.post()验证成功路径通知触发、导航发生、query 失效验证错误路径错误通知触发验证isPending状态禁用触发按钮。6.7 从data-stores迁移fetcher/query 模式相同但有两点差异不需要桥接——消费者已经用 React Query只需换 import单个 commit——没有 Redux 清理。// Before import { Reader } from automattic/data-stores; const { data, isFetching } Reader.useReadFeedSiteQuery( siteId ); // After import { readFeedSiteQuery } from automattic/api-queries; import { useQuery } from tanstack/react-query; const { data, isFetching } useQuery( readFeedSiteQuery( siteId ) );更新完消费者后从packages/data-stores/src/reader/queries/删除该 hook并从 Reader barrelpackages/data-stores/src/reader/index.ts移除其导出。仓库中packages/data-stores/src/reader/queries/use-site-subscriptions-query.ts展示了中间态它已从automattic/api-queries引入siteSubscriptionsQuery并基于其 queryKey 前缀同时仍用callApiisLoggedIn参数自定义queryFn实现useInfiniteQuery分页——这正是共享 query 选项 局部逻辑留在>grep -r isRequestingXxx client/ --include*.{ts,tsx,js,jsx} -l用 React Query 状态替换 Redux 请求状态// Before const isLoading useSelector( isRequestingXxx ); // After const { isLoading } useQuery( readXxxQuery() );React Query 加载状态语义isLoading—— 首次加载、尚无缓存数据对应旧isRequesting首次挂载isFetching—— 任何 fetch含后台 refetch用于细微指示器isPending—— 尚无数据query 被禁用或首次加载迁移connect()HOC函数组件mapStateToProps数据 selector →useSelector()mapStateToProps请求状态isRequesting*→ 解构的useQuery()状态mapDispatchToProps请求 actionrequestXxx→整体删除React Query 负责请求mapDispatchToProps非请求 action →useDispatch() 直接 dispatch移除connect()HOC。类组件hooks不能在类组件内运行。不要把类组件重写成函数组件作为数据迁移的一部分——那是另一项重构。改用函数式 HOC 包一层调用useQuery()并把结果作为 props 转发connect()仍然只映射 Redux 数据。决策树函数组件 connect()→ 转 hooks彻底移除 HOC类组件 connect()→ 加函数式包装层承载 queryconnect()保留给 Redux 数据类组件是 slice 的唯一消费者→ 考虑用一个 HOC 同时取代connect()和桥接见 SKILL.md 的 Evaluate removing the>赞分享前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载相关推荐wp-calypso React Query 迁移实战Mutation增删改的完整实现与最佳实践wp calypso React Query 迁移实战Mutation增删改的完整实现与最佳实践 本指南基于 wp calypso 仓库中 .claude前端CMSwp-calypso Reader 站点通知设置组件SiteNotificationSettings解析从 Props 到数据流的完整指南wp calypso Reader 站点通知设置组件SiteNotificationSettings解析从 Props 到数据流的完整指南 导读 本文以前端CMSwp-calypso数据层10年演进史从EventEmitter到Flux再到data-stores的5大时代wp calypso数据层10年演进史从EventEmitter到Flux再到data stores的5大时代 wp calypso 是 WordPress.前端CMS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Python Word 编程:插入、更新和管理域
Python Word 编程:插入、更新和管理域

域(Field)是 Word 中的一种特殊元素,它的显示内容由域代码和域结果组成,可以根据环境或文档状态动态更新。例如页脚里的"第 X 页"、目录页码、章节引用,以及"如果数量大于 100 则显示某段文字"这种… · 2026/9/23 3:33:52

2026最新众数算法避坑指南:面试不再被问懵
2026最新众数算法避坑指南:面试不再被问懵

2026最新众数算法避坑指南:面试不再被问懵 是不是觉得刷了一百道题,真到了项目里还是卡壳?很多应届生反馈,看了一堆教程还是不会写项目,尤其是处理数据分布时,一碰到“众数”这个需求,脑子就是一片空白。别慌,这不是你的错,是传统教程太浅,没讲… · 2026/9/23 3:33:46

X光安检数据集实战:VOC/COCO/YOLO格式转换与YOLO训练调参指南
X光安检数据集实战:VOC/COCO/YOLO格式转换与YOLO训练调参指南

简介:面向目标检测学习者和安检场景开发者,这份资源汇集1000张真实X光安检图片,画面场景丰富,标注框质量高,同时给出VOC、COCO、YOLO三种常见格式标签,标签按格式分目录存放,便于切换训练框架&a… · 2026/9/23 3:33:46

GitHub日榜深度解析:从热榜项目到本地部署的避坑指南
GitHub日榜深度解析:从热榜项目到本地部署的避坑指南

先说结论:就算你不是天天泡开源社区的人,只要你的工作里有一丁点和开发、自动化、AI工具相关,每天花十分钟过一遍 GitHub 日榜,比刷两小时信息流有价值得多。今天(2026年9月19日)我又把日榜完整翻了一遍&am… · 2026/9/23 4:18:41

拒绝背八股文:用明星QQ号码大全思维,搞定编程入门到精通
拒绝背八股文:用明星QQ号码大全思维,搞定编程入门到精通

拒绝背八股文:用明星QQ号码大全思维,搞定编程入门到精通 很多开发者卡在 学会语法却不知怎么搭项目 这一步。你背熟了 for 循环,记得 try-catch… · 2026/9/23 4:18:41

搭建GitHub日榜趋势速报:从数据抓取到自动化推送全指南
搭建GitHub日榜趋势速报:从数据抓取到自动化推送全指南

每天早上一睁眼,我干的第一件事不是刷朋友圈,而是翻一份自己搭好的 GitHub 日榜趋势速报。这份速报会自动抓取当天热度上升最快的开源项目,整理成清单,再把其中最值得看的几个单独标出来,顺便生成一段简洁的评论。坚持… · 2026/9/23 4:18:34

3步搞定FiUI选型,从入门到精通避坑指南
3步搞定FiUI选型,从入门到精通避坑指南

3步搞定FiUI选型,从入门到精通避坑指南 刚学完语法,打开IDE却不知怎么搭项目?这是90%新手的噩梦。很多人对着FiUI文档发呆,感觉代码会写,但一落地就卡壳,根本不知道如何把零散的组件拼成完整应用。… · 2026/9/23 4:18:28

在线压缩踩坑实录:3个致命错误让新手避坑指南失效
在线压缩踩坑实录:3个致命错误让新手避坑指南失效

在线压缩踩坑实录:3个致命错误让新手避坑指南失效 上周帮一个刚入职的兄弟看代码,他问我:“为什么我在本地测试压缩文件没问题,一到线上就炸?”我一看代码,笑而不语。这哥们儿面试被问“Gzip压缩原理”时答得磕磕绊绊,实际开发更是把在线压缩当成… · 2026/9/23 4:18:28

2026阿里并发编程全优笔记:从JMM到线程池的实战梳理
2026阿里并发编程全优笔记:从JMM到线程池的实战梳理

如果你啃过几本并发编程相关的书,也刷过一些技术博客,大概率会有和我当时一样的感受:每个知识点单独拎出来好像都认识,synchronized 知道、volatile 也听说过,可一旦要把它们组合起来解决线上问题,脑子里的… · 2026/9/23 4:18:22

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码