- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
Query Posts 是 WordPress.com 前端应用 wp-calypso 中用于管理文章(Posts)查询与数据拉取的核心 React 组件。它通过零渲染的声明式设计,将"文章列表 / 单篇文章 / 全站文章"三类网络请求抽象为简单的组件挂载行为,配合全局 Redux 状态树完成数据的获取与共享。读完本文,你将掌握该组件的全部 Props 语义、底层 action 调用链、防重复请求机制,以及它在统计模块中的真实落地用法。
组件定位与核心设计理念
Query Posts 位于 client/components/data/query-posts/,是 wp-calypso "Query 组件"家族(client/components/data/下还有 query-post-stats、query-jetpack-modules 等同系列组件)中的一员,负责所有文章类数据的前端拉取。
它遵循一个非常鲜明的设计原则:组件不接收任何 children,也不向页面渲染任何 DOM 元素。组件挂载的唯一职责是发起数据请求,而请求结果统一落入全局应用状态(Redux store),由挂载在同一层级的兄弟组件通过 selector 消费。这种"请求组件与展示组件分离"的模式,让数据获取逻辑可以在任何页面位置复用,而不必关心视图层如何渲染。
从源码 index.jsx 可以看到,组件的渲染函数直接return null,其全部逻辑都发生在useEffect中:
function QueryPosts( { siteId, postId, query } ) { const dispatch = useDispatch(); const memoizedQuery = useMemoCompare( query, isShallowEqual ); useEffect( () => { dispatch( request( siteId, postId, memoizedQuery ) ); }, [ dispatch, siteId, postId, memoizedQuery ] ); return null; }组件使用 React Hooks 实现:useDispatch获取 Redux 的 dispatch 能力,useEffect在 Props 变化时触发请求,而useMemoCompare(来自 client/lib/use-memo-compare/index.ts)配合@wordpress/is-shallow-equal对query对象做浅比较,保证只有 query 内容真正变化时才重新发起请求,避免因对象引用变化导致的重复拉取。
基本用法
在需要文章数据的页面中,将QueryPosts作为兄弟组件渲染,传入siteId和query。以下示例来自原文档,展示了如何用它在自定义文章列表中拉取并渲染数据:
import QueryPosts from 'calypso/components/data/query-posts'; import MyPostsListItem from './list-item'; export default function MyPostsList( { posts } ) { return ( <div> <QueryPosts siteId={ 3584907 } query={ { search: 'Themes' } } /> { posts.map( ( post ) => { return <MyPostsListItem key={ post.global_ID } post={ post } />; } ) } </div> ); }注意QueryPosts本身不产出任何可见 UI,posts数据是通过 connect/useSelector 从全局状态中读取的;组件只是确保请求已被发出、数据已就绪。post.global_ID是 wp-calypso 状态树中文章的全局唯一标识,用于列表 key。
Props 详解
siteId
| 项 | 值 |
|---|---|
| 类型 | Number |
| 是否必填 | 否 |
| 默认值 | null |
需要查询文章的站点 ID。传入后执行"指定站点"的文章查询;若不传(为null),则退化为"当前用户所有站点"的全局文章查询。
postId
| 项 | 值 |
|---|---|
| 类型 | Number |
| 是否必填 | 否 |
| 默认值 | null |
需要查询的单篇文章 ID。一旦提供,组件将执行单篇文章查询(而非列表查询),用于获取该站点的某篇具体文章。
query
| 项 | 值 |
|---|---|
| 类型 | Object |
| 是否必填 | 否 |
| 默认值 | null |
发起文章请求时使用的查询参数对象,会被原样透传给 WP.com REST API。常用参数包括search(关键词搜索)、number(每页数量)、order/order_by(排序)、type(文章类型)与status(发布状态)等,其默认值定义在 client/state/posts/constants.js:
export const DEFAULT_POST_QUERY = { context: 'display', http_envelope: false, pretty: false, number: 20, offset: 0, page: 1, order: 'DESC', order_by: 'date', type: 'post', status: 'publish', sticky: 'include', search: '', };这些默认值与 API 侧语义保持一致:默认拉取 20 篇已发布文章,按日期倒序排列。未传入query时(null),组件会以空对象发起请求,由 API 侧应用上述默认行为。
三种数据获取模式的底层调用链
QueryPosts的请求分派逻辑集中在 index.jsx 的request函数中,按 Props 组合分为三条路径:
const request = ( siteId, postId, query ) => ( dispatch, getState ) => { const state = getState(); if ( ! siteId && ! isRequestingPostsForQuery( state, null, query ) ) { dispatch( requestAllSitesPosts( query ) ); return; } if ( ! postId && ! isRequestingPostsForQuery( state, siteId, query ) ) { dispatch( requestSitePosts( siteId, query ) ); return; } if ( ! isRequestingSitePost( state, siteId, postId ) && postId > 0 ) { dispatch( requestSitePost( siteId, postId ) ); } };三条路径的判定优先级为:全站查询 → 站点列表查询 → 单篇文章查询,对应关系如下:
| Props 组合 | 触发的 action | 底层 API 端点 | 对应源文件 |
|---|---|---|---|
仅query(无siteId、无postId) | requestAllSitesPosts(query) | GET /me/posts | request-all-sites-posts.js |
siteId+query(无postId) | requestSitePosts(siteId, query) | GET /sites/{siteId}/posts | request-site-posts.js |
siteId+postId | requestSitePost(siteId, postId) | GET /sites/{siteId}/posts/{postId} | request-site-post.js |
前两条路径最终都汇聚到统一的 request-posts.js,它负责发出网络请求并分发三类状态 action:
export function requestPosts( siteId, query = {} ) { return ( dispatch ) => { dispatch( { type: POSTS_REQUEST, siteId, query } ); const endpoint = siteId ? `/sites/${ siteId }/posts` : '/me/posts'; return wpcom.req .get( endpoint, { ...query } ) .then( ( { found, posts } ) => { dispatch( receivePosts( posts ) ); dispatch( { type: POSTS_REQUEST_SUCCESS, siteId, query, found, posts } ); } ) .catch( ( error ) => { dispatch( { type: POSTS_REQUEST_FAILURE, siteId, query, error } ); } ); }; }requestSitePosts在缺少siteId时直接返回null(request-site-posts.js),而requestAllSitesPosts等价于调用requestPosts( null, query )(request-all-sites-posts.js),二者语义在底层统一。
单篇文章路径则独立走 request-site-post.js:先分发POST_REQUEST,再通过wpcom.site( siteId ).post( postId ).get()拉取单篇数据,成功后用receivePost写入状态树,并分发POST_REQUEST_SUCCESS;失败则分发POST_REQUEST_FAILURE。
防重复请求机制:序列化查询 + 请求中标记
request函数在每次分派前都会先调用 selector 检查对应请求是否已经在进行中,从源头避免并发重复请求。其判断依据是 is-requesting-posts-for-query.js 与 is-requesting-site-post.js:
// is-requesting-posts-for-query.js export function isRequestingPostsForQuery( state, siteId, query ) { const serializedQuery = getSerializedPostsQuery( query, siteId ); return !! state.posts.queryRequests[ serializedQuery ]; } // is-requesting-site-post.js export function isRequestingSitePost( state, siteId, postId ) { if ( ! siteId ) { return null; } if ( ! state.posts.siteRequests[ siteId ] ) { return false; } return !! state.posts.siteRequests[ siteId ][ postId ]; }列表查询使用"序列化查询串"作为状态键:查询对象先经 get-normalized-posts-query.js 归一化(剔除与DEFAULT_POST_QUERY相同的字段),再由 get-serialized-posts-query.js 做JSON.stringify,有siteId时拼接为siteId:serializedQuery形式的键。这样语义相同的查询(如{ search: '' }与{})会命中同一个键,从而正确复用请求状态。
在状态层,client/state/posts/reducer.js 中的两个 reducer 负责维护"请求中"标记:queryRequests以序列化查询为键、以POSTS_REQUEST === action.type为值记录列表请求状态(reducer.js#L113-L125);siteRequests以siteId -> postId -> boolean的嵌套结构记录单篇请求状态(reducer.js#L90-L103)。请求发出、成功、失败三类 action 都会更新对应标记,确保标记在请求结束后复位,下一次挂载可重新拉取。
项目中的真实使用案例
Query Posts 目前主要被 wp-calypso 的统计(Stats)模块采用,覆盖周/月/邮件详情页与单篇文章详情页:
- stats-post-detail/index.jsx:
{ siteId && ! isPostHomepage && <QueryPosts siteId={ siteId } postId={ postId } /> },在文章统计详情页按站点与文章 ID 拉取单篇文章,供摘要与预览区域消费; - stats-detail-weeks/index.jsx 与 stats-detail-months、stats-email-detail:同样以
siteId+postId组合挂载,为周/月/邮件明细视图准备文章数据; - all-time-highlights-section/post-cards-group.tsx:
<QueryPosts siteId={ siteId } postId={ topViewedPost.id } query={ {} } />,在"历史时刻"高亮区块中按需为排名文章发起单篇查询。
这些页面通常在兄弟位置同时挂载QueryPostStats(文章统计)与QueryPosts(文章本体),二者各司其职、互不干扰,是"请求组件与展示组件分离"架构的直接体现。需要查看更多同类 Query 组件的读者,可浏览 client/components/data/ 目录下的其他 README。
最佳实践与注意事项
- 放在数据消费组件的兄弟位置:
QueryPosts不渲染 UI,务必与读取state.posts的组件平级挂载,并确保消费数据的 selector(如getSitePost、getPostsForQuery)基于同一siteId/query取值。 - 用
post.global_ID作为列表 key:列表数据来自不同站点时ID可能重复,使用全局唯一标识可避免 key 冲突。 query对象应保持稳定:组件用浅比较判定 query 是否变化,若每次渲染都新建对象会导致useMemoCompare不断更新引用并重复请求;建议将查询对象提升为常量或使用useMemo。- 理解优先级:同时传入
siteId与postId时走单篇查询,query参数将被忽略;需要列表查询时不要传postId。 - 全站查询的代价:不传
siteId会请求/me/posts(当前用户全部站点的文章),数据量与耗时都更大,仅应在明确需要跨站点聚合时使用。 - 请求状态已内建去重:同一
siteId+ 序列化 query 的并发挂载不会重复发请求,selector 与 reducer 层已做好防重保护,无需在业务侧额外加锁。
综上,Query Posts 通过极简的声明式 API,把 wp-calypso 中"文章数据从何而来"的问题收敛为一个可复用的挂载动作,配合 client/state/posts/ 下的 actions、selectors 与 reducer,构成了完整、可预测的文章数据获取链路。
- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
相关推荐
深入解析 wp-calypso 的 QueryPostStats 组件:文章统计数据的声明式数据获取方案
深入解析 wp calypso 的 QueryPostStats 组件:文章统计数据的声明式数据获取方案 导读 在 WordPress.com 的 JavaSc
前端CMSwp-calypso Query Theme 组件解析:单主题数据获取的声明式解决方案
wp calypso Query Theme 组件解析:单主题数据获取的声明式解决方案 Query Theme 是 wp calypso(WordPress.c
前端CMSwp-calypso 站点统计声明式数据获取组件 QuerySiteStats 完全指南
wp calypso 站点统计声明式数据获取组件 QuerySiteStats 完全指南 <QuerySiteStats / 是 wp calypso(Word
前端CMS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考