You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:09:29