如何为用withSelect/getEntityRecords的古腾堡最新文章区块实现分页?
你说得对,在withSelect里直接用apiFetch确实不是正确的做法——因为withSelect需要同步返回状态值,而apiFetch是异步操作,返回的是未解析的Promise,这会导致组件渲染时拿到的不是实际的分页数值,进而引发问题。
下面给你两种可行的解决方案,优先推荐第一种,因为它更贴合Gutenberg的状态管理机制,无需额外发起API请求:
方法1:利用getEntityRecords返回结果的_headers属性
WordPress的Gutenberg数据层在通过getEntityRecords获取REST API数据时,会自动把响应的HTTP头部附加到返回的文章集合(数组)的_headers属性上。你可以直接从这里提取X-WP-Total和X-WP-TotalPages:
修改你的withSelect逻辑如下:
edit: withSelect( function( select ) { const queryParams = { per_page: 1, page: 1 }; const posts = select( 'core' ).getEntityRecords( 'postType', 'post', queryParams ); let numberOfPages = 0; let totalPostCount = 0; // 当posts数据加载完成后,从_headers提取分页信息 if ( posts ) { const responseHeaders = posts._headers; if ( responseHeaders ) { // 转换为整数类型,确保数值可用 numberOfPages = parseInt( responseHeaders.get( 'X-WP-TotalPages' ), 10 ); totalPostCount = parseInt( responseHeaders.get( 'X-WP-Total' ), 10 ); } } return { posts, numberOfPages, totalPostCount, currentPage: queryParams.page, }; } )( function( props ) { if ( ! props.posts ) { return "Loading..."; } if ( props.posts.length === 0 ) { return "No posts"; } // 这里可以直接使用props.numberOfPages和props.totalPostCount return ( <div> { props.posts.map( ( post ) => ( <h3 className={props.className} key={post.id}> {post.title.rendered} </h3> ))} <p>Total pages: {props.numberOfPages}</p> </div> ); } ),
注意:_headers是一个标准的Headers对象,所以要用.get()方法获取指定头部,并且要转换为整数类型,避免后续数值操作出错。
方法2:使用组件内部状态结合apiFetch(兼容备选)
如果因为版本兼容或其他原因无法通过_headers获取头部,可以在区块的edit组件中使用React的状态管理来异步获取分页信息,这样能避免withSelect的同步限制:
( function( blocks, element, data, apiFetch, url ) { const { registerBlockType } = blocks; const { withSelect } = data; const { useState, useEffect } = element; registerBlockType( 'mdwpb/latest-posts', { title: 'latest posts', icon: 'megaphone', category: 'widgets', attributes: { currentPage: { type: 'number', default: 1, }, perPage: { type: 'number', default: 1, }, }, edit: withSelect( function( select, props ) { const { currentPage, perPage } = props.attributes; return { posts: select( 'core' ).getEntityRecords( 'postType', 'post', { per_page: perPage, page: currentPage } ), }; } )( function( props ) { const { posts, attributes, setAttributes } = props; const [ numberOfPages, setNumberOfPages ] = useState( 0 ); const [ totalPosts, setTotalPosts ] = useState( 0 ); const { currentPage, perPage } = attributes; // 当分页参数变化时,重新请求分页信息 useEffect( () => { const fetchPaginationMeta = async () => { const rawResponse = await apiFetch( { path: url.addQueryArgs( 'wp/v2/posts', { per_page: perPage, page: currentPage } ), parse: false, // 不自动解析JSON,保留原始Response对象 } ); // 提取并更新分页状态 const pageCount = parseInt( rawResponse.headers.get( 'X-WP-TotalPages' ), 10 ); const totalCount = parseInt( rawResponse.headers.get( 'X-WP-Total' ), 10 ); setNumberOfPages( pageCount ); setTotalPosts( totalCount ); }; fetchPaginationMeta(); }, [ currentPage, perPage ] ); if ( ! posts ) { return "Loading..."; } if ( posts.length === 0 ) { return "No posts"; } // 分页控制按钮逻辑 const goToPrevPage = () => { if ( currentPage > 1 ) { setAttributes( { currentPage: currentPage - 1 } ); } }; const goToNextPage = () => { if ( currentPage < numberOfPages ) { setAttributes( { currentPage: currentPage + 1 } ); } }; return ( <div> { posts.map( ( post ) => ( <h3 className={props.className} key={post.id}> {post.title.rendered} </h3> ))} <div className="pagination-controls"> <button onClick={goToPrevPage} disabled={currentPage === 1}> ← Previous Page </button> <span>Page {currentPage} / {numberOfPages}</span> <button onClick={goToNextPage} disabled={currentPage >= numberOfPages}> Next Page → </button> </div> </div> ); } ), } ); }( window.wp.blocks, window.wp.element, window.wp.data, window.wp.apiFetch, window.wp.url ) );
这种方法把异步操作放在React的useEffect钩子中,符合组件生命周期规范,同时通过区块属性管理当前页码,实现完整的分页交互。
内容的提问来源于stack exchange,提问作者Bornie
相关产品推荐
相关产品推荐

