- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
导读
本文围绕 WordPress.com 前端仓库 wp-calypso 中的数据请求型组件QueryPurchaseCancellationOffers展开,讲解如何通过声明式渲染来触发"产品取消优惠(cancellation offers)"的网络请求、如何读取请求结果与加载状态,并深入其背后的 Redux 数据流:actions、data-layer API 请求、reducer 与 selectors。读完本文,你将掌握该组件的 Props 契约、挂载即请求的触发机制、以及围绕siteId与purchaseId的完整状态管理方案,可直接在取消订阅挽留(save / win-back)类业务场景中落地复用。
一、组件定位:不渲染任何 UI 的"请求触发器"
QueryPurchaseCancellationOffers是 wp-calypso 中典型的Query 型组件:它专门负责管理"获取某个购买项可用的取消优惠"这一网络请求的生命周期,自身不渲染任何子元素,也不向页面输出任何可见的 DOM 节点。
在 wp-calypso 的组件目录体系中,这类组件统一位于 client/components/data/ 下,命名约定为Query*,其职责边界非常清晰:
- 只负责发请求:挂载时根据 Props 触发一次数据获取;
- 不负责展示:渲染结果恒为
null; - 依赖全局状态:请求结果、加载中/失败状态统一写入 Redux store,由业务组件通过 selector 自行读取。
围绕取消优惠这一主题,仓库中完整的代码资产分布在以下位置:
| 职责 | 文件路径 |
|---|---|
| 组件本体(含 README) | client/components/data/query-purchase-cancellation-offers/index.tsx 与 client/components/data/query-purchase-cancellation-offers/README.md |
| Redux actions | client/state/cancellation-offers/actions.js |
| Redux reducer | client/state/cancellation-offers/reducer.ts |
| API 请求层(data-layer) | client/state/data-layer/wpcom/cancellation-offers/index.tsx |
| 类型定义 | client/state/cancellation-offers/types.ts |
| selectors | client/state/cancellation-offers/selectors/get-cancellation-offers.js 等 |
二、基础用法:声明式触发优惠请求
组件的使用方式非常直接:渲染组件并传入两个必填 Props,它不接受任何 children,也不会向页面渲染任何元素。
/* eslint-disable */ import QueryPurchaseCancellationOffers from 'calypso/components/data/query-purchase-cancellation-offers'; import getCancellationOffers from 'calypso/state/cancellation-offers/selectors/get-cancellation-offers'; const listProductPrice: React.FC = ( { product } ) => { const cancellationOffers = useSelector( ( state ) => getCancellationOffers( state, product.purchaseId ) ); return ( <div> { product.siteId && product.purchaseId ? <QueryPurchaseCancellationOffers siteId={product.siteId} purchaseId={product.purchaseId} /> : null } </div> ); };示例中的listProductPrice组件演示了标准的数据消费模式:
- 通过
useSelector与getCancellationOffersselector 按purchaseId从 Redux store 中读取已缓存的优惠数组; - 仅当
product.siteId与product.purchaseId都已就绪时,才渲染QueryPurchaseCancellationOffers触发请求,避免在数据缺失时发出无效请求; - 组件本身不渲染内容,优惠数据由外层组件根据自身 UI 需求自行展示。
useSelector来自calypso/state(对应@wordpress/data风格的 hooks 封装),getCancellationOffers的完整实现见 client/state/cancellation-offers/selectors/get-cancellation-offers.js,其内部通过可选链安全读取:
return state.cancellationOffers?.[ purchaseId ]?.offers ?? [];即:store 中不存在该 purchaseId 的记录时返回空数组,保证组件拿到稳定的默认值。
三、Props 契约
组件仅接受两个 Props,且均为必填:
| Name | Required | Type | Description |
|---|---|---|---|
siteId | Yes | Number | 购买项(purchase)所属站点的 ID。 |
purchaseId | Yes | Number | 要获取取消优惠的购买项 ID。 |
siteId与purchaseId之所以缺一不可,是因为二者共同构成了请求的定位信息:
- 在服务端,优惠策略需要同时知道"哪个站点"(
site)和"哪笔购买"(purchase)才能计算出可用的挽留优惠; - 在客户端,reducer 以
purchaseId为 key 组织状态(详见下文),而请求体又需要携带siteId,因此组件必须同时持有两者。
四、组件内部实现:挂载即请求的触发机制
组件的核心逻辑非常精简,完整源码位于 client/components/data/query-purchase-cancellation-offers/index.tsx:
import { useEffect } from 'react'; import { useDispatch, useSelector } from 'calypso/state'; import { fetchCancellationOffers } from 'calypso/state/cancellation-offers/actions'; import isFetchingCancellationOffers from 'calypso/state/cancellation-offers/selectors/is-fetching-cancellation-offers'; interface OwnProps { siteId: number; purchaseId: number; } const QueryPurchaseCancellationOffers = ( { siteId, purchaseId }: OwnProps ) => { const dispatch = useDispatch(); const fetchingCancellationOffers = useSelector( ( state ) => isFetchingCancellationOffers( state, purchaseId ) ); useEffect( () => { if ( siteId && fetchingCancellationOffers === null ) { dispatch( fetchCancellationOffers( siteId, purchaseId ) ); } }, [ dispatch, fetchingCancellationOffers, siteId, purchaseId ] ); return null; }; export default QueryPurchaseCancellationOffers;其工作机制可以拆解为三层:
状态探测:挂载时通过
isFetchingCancellationOffers( state, purchaseId )读取该 purchaseId 的请求状态。该 selector 的实现(见 client/state/cancellation-offers/selectors/is-fetching-cancellation-offers.js)为:return state.cancellationOffers?.[ purchaseId ]?.isFetching ?? null;关键点在于:从未请求过时返回
null,请求中返回true,请求结束返回false。组件正是利用"null代表尚未发起请求"这一语义来决定是否派发 action。条件触发:在
useEffect中,只有当siteId存在且fetchingCancellationOffers === null时才dispatch( fetchCancellationOffers( siteId, purchaseId ) )。这保证了:- 每个 purchaseId 的优惠请求在组件生命周期内只发起一次;
- 请求进行中(
true)或已结束(false)时不会重复触发; - 依赖数组中的
fetchingCancellationOffers使组件在状态变化后自动重新评估,形成"请求完成即收敛"的闭环。
渲染为空:函数直接
return null,不产生任何 DOM。这也是所有 Query 型组件的共同约定——UI 展示完全交给消费方组件。
五、底层数据流:从 action 到 wpcom/v2 API
组件派发的fetchCancellationOffersaction 定义于 client/state/cancellation-offers/actions.js:
export const fetchCancellationOffers = ( siteId, purchaseId ) => ( { type: PURCHASE_CANCELLATION_OFFER_REQUEST, siteId, purchaseId, } );对应的 action types 有 6 个:PURCHASE_CANCELLATION_OFFER_REQUEST、PURCHASE_CANCELLATION_OFFER_RECEIVE、PURCHASE_CANCELLATION_OFFER_REQUEST_FAILURE、PURCHASE_CANCELLATION_OFFER_APPLY、PURCHASE_CANCELLATION_OFFER_APPLY_SUCCESS、PURCHASE_CANCELLATION_OFFER_APPLY_FAILURE(定义于calypso/state/action-types)。
该 action 随后被>const fetchCancellationOffers = ( action ) => { return http( { method: 'GET', path: '/cancellation-offers', apiNamespace: 'wpcom/v2', query: { site: action.siteId, purchase: action.purchaseId, }, retryPolicy: noRetry(), }, action ); };
请求细节值得注意:
- 接口路径:
GET /wpcom/v2/cancellation-offers,查询参数为site(对应siteId)与purchase(对应purchaseId); - 重试策略:显式指定
retryPolicy: noRetry(),即优惠查询失败时不做自动重试,避免在用户挽留场景下产生重复请求或额外计费风险; - 成功/失败回写:
onFetchSuccess派发PURCHASE_CANCELLATION_OFFER_RECEIVE并携带原始响应数组,onFetchError派发PURCHASE_CANCELLATION_OFFER_REQUEST_FAILURE。
同一>const cancellationOffersReducer = combineReducers( { isFetching, error, offers, isApplying, applyError, applySuccess, } ); const reducer = keyedReducer( 'purchaseId', cancellationOffersReducer ); export default withStorageKey( 'cancellationOffers', reducer );
核心要点:
keyedReducer( 'purchaseId', ... ):整个 reducer 按purchaseId分桶存储,每个购买项拥有独立的isFetching / error / offers / isApplying / applyError / applySuccess状态切片,互不干扰;withStorageKey( 'cancellationOffers', ... ):将该模块挂载到全局 state 的cancellationOffers字段下(来自@automattic/state-utils),这也是 selector 中state.cancellationOffers?.[ purchaseId ]路径的由来;isFetching初始值为null:对应组件中"未请求过"的判断语义;收到PURCHASE_CANCELLATION_OFFER_REQUEST变为true,收到RECEIVE或REQUEST_FAILURE变回false;offers的归一化:收到响应后通过createCancellationOfferMap将下划线风格的 API 字段映射为驼峰风格的内部类型。
七、数据模型:优惠对象的结构
API 响应与内部类型定义于 client/state/cancellation-offers/types.ts:
export interface CancellationOffer { currencyCode: string; discountPercentage: number; discountedPeriods: number; formattedPrice: string; originalPrice: number; rawPrice: number; } export interface CancellationOfferAPIResponse { currency_code: string; discount_percentage: number; discounted_periods: number; formatted_price: string; original_price: number; raw_price: number; }字段含义说明:
| 字段 | 说明 |
|---|---|
currencyCode/currency_code | 优惠价格的货币代码,如USD |
discountPercentage/discount_percentage | 折扣百分比 |
discountedPeriods/discounted_periods | 享受折扣的计费周期数 |
formattedPrice/formatted_price | 已格式化的展示价格字符串(可直接用于 UI) |
originalPrice/original_price | 原始价格 |
rawPrice/raw_price | 未经格式化的原始价格数值 |
映射逻辑位于 reducer 的mapResponseObject函数中,逐字段完成 snake_case 到 camelCase 的转换,保证组件层只与类型安全的内部结构打交道。
八、配套 selectors 与最佳实践
除getCancellationOffers与isFetchingCancellationOffers外,client/state/cancellation-offers/selectors/ 还提供了一组配套读取函数:
get-cancellation-offer-apply-error.js:读取应用优惠时的错误信息;get-cancellation-offer-apply-success.js:读取优惠应用是否成功;is-applying-cancellation-offer.js:优惠应用请求是否进行中。
综合使用时的推荐实践:
- 先渲染 Query 组件,再读取数据:将
QueryPurchaseCancellationOffers放在页面/区块顶部,配合useSelector读取offers,即可实现"进入页面自动加载、加载完成后自动展示"; - 用
isFetching控制加载态:isFetchingCancellationOffers返回true时展示 loading 占位,避免空白闪烁; - 以
purchaseId作为缓存键:得益于keyedReducer的设计,多个购买项共存于一个页面时,各购买项的优惠数据天然隔离; - 空值兜底:
getCancellationOffers在无数据时返回[],渲染层无需额外判空逻辑。
九、小结
QueryPurchaseCancellationOffers是 wp-calypso Query 型组件范式的一个典型代表:以两个必填 Props(siteId、purchaseId)为契约,通过useEffect监听isFetching === null实现"挂载即请求、一次请求不重发",再配合 actions、data-layer 与按purchaseId分桶的 reducer,形成了一条从 UI 到wpcom/v2/cancellation-offersAPI 再到全局状态的完整数据链路。理解该组件,即可举一反三地掌握 wp-calypso 中所有Query*数据请求组件的设计模式与接入方法。
- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
相关推荐
wp-calypso 中 QueryIntroOffers 组件的深入解析:产品入门优惠(Introductory Offers)的网络请求管理实践
wp calypso 中 QueryIntroOffers 组件的深入解析:产品入门优惠(Introductory Offers)的网络请求管理实践 本文围绕
前端CMSwp-calypso 数据获取组件 QuerySites 完全指南:React + Redux 下的站点网络请求管理
wp calypso 数据获取组件 QuerySites 完全指南:React + Redux 下的站点网络请求管理 本篇技术指南聚焦 WordPress.co
前端CMSwp-calypso 数据查询组件深度解析:QuerySiteProducts 站点产品数据获取实战指南
wp calypso 数据查询组件深度解析:QuerySiteProducts 站点产品数据获取实战指南 <QuerySiteProducts / 是 Word
前端CMS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考