☰
wp-calypso 站点数据管理模块(client/state/sites)深入指南:Actions、Reducers 与 Selectors 全解析
2026/9/29 2:47:22 网站建设 项目流程
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

导读

client/state/sites是 wp-calypso(The JavaScript and API powered WordPress.com 客户端)中负责站点(Site)数据管理的核心 Redux 模块,它负责把当前用户可见的所有站点对象、各站点可用方案(Plans)等数据组织进全局状态树。本文以此模块的官方文档为主体,结合仓库内真实源码,完整讲解其 Actions(receiveSite/receiveSites 等)、Reducers(items/plans 等)以及 Selectors 的用法与底层实现,帮助你理解 wp-calypso 中"站点数据从 API 到 Redux 再到 UI"的完整链路。

模块总览:A module for managing site data

在 wp-calypso 的 Redux 状态树设计中,sites是一个独立的顶层切片(slice),专门管理站点数据。其入口文档位于 client/state/sites/README.md,模块由以下几类文件组成:

  • actions.js:定义向全局状态写入站点数据的 Action 构造器与 thunk(异步请求 Action);
  • reducer.js:定义items、plans等 Reducer,决定数据如何落进状态树;
  • selectors/:定义从状态树读取站点数据的 Selector 集合;
  • 子模块目录:plans/(站点方案)、products/(站点产品)、domains/(站点域名)、features/、intro-offers/、launch/(站点启动流程)以及hooks/(基于站点的 React Hooks)。

整体数据流遵循标准 Redux 模式:Action 构造器(或 thunk)→ dispatch → Reducer → 状态树 → Selector 读取。下面的章节将沿这条链路逐层展开。

Actions:如何把站点写入全局状态

文档指出,Actions 需要与 Redux store 实例的dispatch函数配合使用,用于操纵当前全局状态。实现位于 client/state/sites/actions.js。

receiveSite( site: Object )

该 Action 把一个站点对象加入"已知站点集合"。文档给出的用法示例为:

import { receiveSite } from 'calypso/state/sites/actions'; dispatch( receiveSite( { ID: 2916284, name: 'WordPress.com Example Blog' } ) );

对应源码实现(actions.js)是一个非常纯粹的对象构造器,不发起任何网络请求:

export function receiveSite( site ) { return { type: SITE_RECEIVE, site, }; }

它返回一个SITE_RECEIVE类型的 Action,Reducer 收到后会将该站点对象以site.ID为键写入sites.items。注意示例中传入的站点对象只需包含ID与name即可合法入树——这正是 schema.js 中sitesSchema对每个站点条目要求的最低字段(required: [ 'ID', 'name' ])。

文档之外的兄弟 Action:receiveSites

与单站点接收对应的批量版本receiveSites( sites )(actions.js)签发SITES_RECEIVE,用于一次写入整个站点数组。在 Reducer 中,SITES_RECEIVE与SITE_RECEIVE语义有微妙差别(详见下文 Reducer 一节):前者表示"收到了用户的全部站点",会替换整个items状态;后者表示"收到单个站点",会合并进现有状态。

异步 thunk:requestSites与requestSite

虽然 README 只列举了receiveSite,但实际模块中更常用的是负责发起网络请求的 thunk,它们是理解整条数据链的关键:

requestSites()(actions.js)通过wpcom.req.get( '/me/sites' )拉取当前用户所有可见站点,请求参数包含:

  • apiVersion: '1.2'
  • site_visibility: 'all'(含不可见站点)
  • include_domain_only: true
  • site_activity: 'active'
  • fields: SITE_REQUEST_FIELDS与options: SITE_REQUEST_OPTIONS(字段白名单,见 constants.js,下文详解)
  • filters:来自配置项site_filter(config( 'site_filter' )),用于按特性过滤站点

请求成功后,如果当前环境是 Jetpack Cloud(isJetpackCloud()),还会额外过滤掉 P2 站点、Simple 非 Classic 站点和 Garden 站点,然后调用dispatch( receiveSites( ... ) )写入状态,最后签发SITES_REQUEST_SUCCESS;失败则签发SITES_REQUEST_FAILURE。

requestSite( siteFragment, atomicCapabilitiesRetriesLeft = 3 )(actions.js)支持传入站点 ID 或 slug(siteFragment),通过wpcom.site( siteFragment ).get( query )拉取单个站点。它内部做了几个值得注意的处理:

  • 强制 wpcom 数据源:当 Atomic 站点的请求被代理到站点自身 Jetpack、导致difm_lite_site_options缺失时,会用force: 'wpcom'重新请求;当 Jetpack JSON API 方法缺失(ApiNotFoundError/JetpackNotFoundError,或返回 403 "API calls to this blog have been disabled.")时同样强制回源 wpcom;
  • 能力(capabilities)传播等待:站点从普通站点迁移到 Atomic 后,manage_options等能力在服务端可能不会立即同步。源码检测到"站点刚变为 Atomic 且管理能力暂缺"时,会先receiveSite写入当前数据让 UI 及时响应,然后延迟 2 秒递归重试requestSite,最多重试 3 次(atomicCapabilitiesRetriesLeft),待服务端能力传播完成后再以准确数据覆盖;
  • 无能力不入树:请求成功后,只有带site.capabilities的站点才会被写入状态(if ( site && site.capabilities )),并从站点对象中剔除_headers字段(omit( site, '_headers' ))再入库。

删除/离开站点的配套 Actions

receiveDeletedSite( siteId )与receiveLeaveSite( siteId )是两个 thunk(actions.js),各自 dispatch 对应 Action 后还会重新拉取当前用户信息(dispatch( fetchCurrentUser() )),以同步用户可管理站点数量的变化。完整的删除/离开流程由deleteSite( siteId )与leaveSite( siteId, userId )驱动:它们先调用wpcom.req.post删除接口,成功后触发上述 receive Action 并弹出成功通知;失败时针对active-subscriptions、p2-hub-has-spaces、user_owns_domain_subscription等业务错误给出专门的中文提示(如"必须先取消有效订阅才能删除站点"),并埋点 Tracks 事件(calypso_leave_blog_success/failure等)。

Reducers:站点数据在状态树中的落点

README 指出,模块内的 Reducer 会在全局状态树的sites之下新增如下键:items(所有已知站点,按站点 ID 索引)与plans(某站点的可用方案,按站点 ID 索引)。查看 reducer.js 可以看到完整的combineReducers组合:

export default combineReducers( { domains, requestingAll, introOffers, items, plans, products, features, requesting, hasAllSitesList, jetpackSiteDisconnected, isRequestingJetpackSitesFeatures, launch, } );

即实际状态树包含 12 个键,README 中提及的items与plans是其中最重要的两个,其余键用于跟踪请求状态、产品、域名、功能开关等。

items:所有已知站点,按 ID 索引

itemsReducer(reducer.js)用withSchemaValidation( sitesSchema, ... )包裹,即每次状态变化都会用 schema.js 中的sitesSchema校验,非法数据会被拦截。sitesSchema使用patternProperties限定键必须匹配^\d+$(纯数字站点 ID),并规定每个站点条目至少包含ID与name,其余字段(URL、jetpack、icon、is_private、capabilities、plan、updates、lang等)按类型约束校验,且additionalProperties: false禁止多余字段。

Reducer 处理的关键分支包括:

  • SITE_RECEIVE/SITES_RECEIVE(reducer.js):核心写入逻辑。通过isEqual(fast-deep-equal)判断站点对象是否发生变化,未变化则跳过以保持引用稳定;变化时先浅拷贝再写入。关键语义区别在于:SITES_RECEIVE用空对象{}作为归并起点(替换全部站点),SITE_RECEIVE则以现有state为起点(合并进单站点)。
  • SITE_LEAVE_RECEIVE/SITE_DELETE_RECEIVE/JETPACK_DISCONNECT_RECEIVE:从items中移除对应siteId(omit( state, action.siteId ))。
  • SITE_RESET:移除单站点条目,配合resetSiteAction 使用。
  • ODYSSEY_SITE_RECEIVE:处理 Odyssey(Jetpack 独立统计界面)推送的站点信息,与 WPCOM 数据合并,以 Odyssey 数据为最新事实源(例如options.is_commercial只在 WPCOM 数据中存在,需保留)。
  • SITE_SETTINGS_UPDATE/SITE_SETTINGS_RECEIVE:当站点设置更新时,同步派生字段——blog_public === -1映射为is_private: true;wpcom_public_coming_soon/wpcom_coming_soon任一为 1 映射为is_coming_soon: true;site_icon更新会改写站点的icon.media_id(且刻意不保留图标 URL,把 URL 解析负担交给 Selector)。
  • SITE_PLUGIN_UPDATED:插件更新成功后递减updates.plugins与updates.total计数。
  • SITE_FRONT_PAGE_UPDATE:把首页选项(show_on_front、page_on_front、page_for_posts)合并进options。
  • SITE_MIGRATION_STATUS_UPDATE:更新site_migration.status与last_modified。
  • SITE_PURCHASES_UPDATE:将购买记录写入site.products。
  • THEME_ACTIVATE_SUCCESS:主题激活成功后更新options.theme_slug。

此外,items还有一个"延迟初始化"约定:初始状态为null,只有当收到SITE_RECEIVE/SITES_RECEIVE/ODYSSEY_SITE_RECEIVE/SITE_RESET时才从null转为对象(reducer.js),这是为了配合withSchemaValidation在首次数据到达前不执行校验。

plans:站点可用方案,按 ID 索引

plansReducer 由 client/state/sites/plans/reducer.js 提供,README 指向了子目录文档 client/state/sites/plans/README.md。其状态形态与items不同:每个站点 ID 对应一个"站点方案状态对象":

export const initialSiteState = { data: null, error: null, hasLoadedFromServer: false, isRequesting: false, };

对应四种 Action:

  • SITE_PLANS_FETCH:置isRequesting: true;
  • SITE_PLANS_FETCH_COMPLETED:置hasLoadedFromServer: true、isRequesting: false,并把action.plans(已通过 assembler 归一化的方案数组)写入data;
  • SITE_PLANS_FETCH_FAILED:写入error;
  • SITE_PLANS_REMOVE:移除该站点的方案状态(omit( state, action.siteId ))。

数据归一化:plans 的 assembler

子文档 README 提到"Consultassembler.jsfor the details"。查看 client/state/sites/plans/assembler.js,createSitePlanObject把/sites/$site/plans返回的 snake_case 原始字段转换为组件友好的 camelCase 对象,例如:

原始字段(API)归一化字段(assembler 输出)类型转换
auto_renewautoRenewBoolean(...)
current_plancurrentPlanBoolean(...)
is_expiredexpired原样
expiryexpiry/expiryDate原样
has_domain_credithasDomainCreditBoolean(...)
intervalintervalNumber(...)
product_slugproductSlug原样
raw_pricerawPrice原样
user_is_owneruserIsOwnerBoolean(...)

值得注意的是availableForDowngrade/availableForUpgrade故意不做布尔强转,让undefined(字段缺失)与false保持区分,供 UI 判断服务端是否给出了升降级目标。

plans 的 Actions 与请求链路

子文档 client/state/sites/plans/README.md 定义了:

  • fetchSitePlans( siteId: Number ):拉取指定站点的方案。源码实现(client/state/sites/plans/actions.js)dispatchSITE_PLANS_FETCH后调用wpcom.req.get( '/sites/${ siteId }/plans', { apiVersion: '1.3' } ),成功时转入fetchSitePlansCompleted,失败时 dispatchSITE_PLANS_FETCH_FAILED并记录错误信息。
  • fetchSitePlansCompleted( siteId: Number, data: Object ):把 API 返回的方案数据写入该站点。源码(actions.js)会用Object.values( plans ?? {} ).map( createSitePlanObject )对原始数据做归一化后再随 Action 发出。

文档示例用法如下(注意第三个参数是"站点方案键值对象",其值会被Object.values取出后逐条归一化):

import { fetchSitePlans, fetchSitePlansCompleted } from 'calypso/state/sites/plans/actions'; dispatch( fetchSitePlans( 555555555 ) ); dispatch( fetchSitePlansCompleted( 555555555, { 1: { /*...*/ }, 1003: { /*...*/ }, 1008: { /*...*/ }, } ) );

配套工具还包括clearSitePlans( siteId )(清除缓存方案)、refreshSitePlans( siteId )(先清后拉)与transferPlanOwnership( siteId, newOwnerUserId )(方案所有权转移,经>

  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

相关推荐

上一篇:FairSeq完整指南:Facebook开源序列建模工具包的10大核心功能解析
下一篇:ijkplayer终极实战:构建企业级视频播放SDK的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询