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

如何在React中为搜索结果整合分页功能(MERN栈)

解决方案:整合MERN栈的搜索与分页功能

明确结论:单个URL完全可以同时携带搜索与分页参数

比如/posts/?searches=react&page=2,这是HTTP GET请求的标准用法,后端可通过req.query同时获取两个参数。


1. 后端(Node.js)代码优化

你的现有代码已在搜索分支加入分页逻辑,但缺少返回总数据条数的逻辑(前端需此计算总页数),同时完善错误处理:

const DEFAULT_PAGE_NUMBER = 1;
const DEFAULT_PAGE_LIMIT = 12;

const getPagination = (query) => {
  const page = Math.abs(parseInt(query.page)) || DEFAULT_PAGE_NUMBER;
  const limit = Math.abs(parseInt(query.limit)) || DEFAULT_PAGE_LIMIT;
  const skip = (page - 1) * limit;

  return { skip, limit, page };
};

const getAllPosts = async (req, res) => {
  try {
    const { skip, limit, page } = getPagination(req.query);
    const { searches } = req.query;

    // 构建查询条件
    let queryCondition = {};
    if (searches) {
      queryCondition.title = { 
        $regex: searches.toString(), 
        $options: "i" // 不区分大小写
      };
    }

    // 并行查询数据列表和总条数,提升效率
    const [posts, totalCount] = await Promise.all([
      Post.find(queryCondition)
        .populate('username', 'username')
        .sort({ createdAt: -1 })
        .skip(skip)
        .limit(limit),
      Post.countDocuments(queryCondition)
    ]);

    // 返回包含分页信息的响应
    res.status(200).json({
      posts,
      currentPage: page,
      totalPages: Math.ceil(totalCount / limit),
      totalCount
    });
  } catch (err) {
    res.status(500).json({ error: '获取数据失败:' + err.message });
  }
};

2. 前端(React.js)整合搜索与分页逻辑

将搜索关键词、当前页码作为状态统一管理,合并数据请求逻辑:

import { useState, useEffect } from 'react';
import axios from 'axios';

const PostList = () => {
  const [posts, setPosts] = useState([]);
  const [searchTerm, setSearchTerm] = useState('');
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(1);
  const [isLoading, setIsLoading] = useState(false);

  // 统一的请求函数,同时处理搜索和分页参数
  const fetchPosts = async () => {
    setIsLoading(true);
    try {
      // 用URLSearchParams构建参数,自动处理特殊字符编码
      const params = new URLSearchParams();
      if (searchTerm.trim()) params.append('searches', searchTerm.trim());
      params.append('page', currentPage);

      const response = await axios.get('/posts/', { params });
      setPosts(response.data.posts);
      setTotalPages(response.data.totalPages);
    } catch (err) {
      console.error('请求失败:', err);
    } finally {
      setIsLoading(false);
    }
  };

  // 搜索关键词变化时,重置页码为1(避免停留在无数据的页码)
  useEffect(() => {
    setCurrentPage(1);
  }, [searchTerm]);

  // 页码或搜索词变化时,重新拉取数据
  useEffect(() => {
    fetchPosts();
  }, [currentPage, searchTerm]);

  // 切换页码的处理函数
  const handlePageChange = (newPage) => {
    if (newPage >= 1 && newPage <= totalPages) {
      setCurrentPage(newPage);
    }
  };

  return (
    <div className="post-container">
      {/* 搜索输入框 */}
      <div className="search-bar">
        <input
          type="text"
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          placeholder="搜索文章标题..."
        />
      </div>

      {/* 文章列表区域 */}
      <div className="posts-list">
        {isLoading ? (
          <p>加载中...</p>
        ) : posts.length === 0 ? (
          <p>暂无相关文章</p>
        ) : (
          posts.map(post => (
            <div key={post._id} className="post-card">
              <h3>{post.title}</h3>
              <p className="author">作者:{post.username.username}</p>
            </div>
          ))
        )}
      </div>

      {/* 分页控件 */}
      {totalPages > 1 && (
        <div className="pagination">
          <button
            onClick={() => handlePageChange(currentPage - 1)}
            disabled={currentPage === 1 || isLoading}
          >
            上一页
          </button>

          {/* 生成页码按钮 */}
          {Array.from({ length: totalPages }, (_, idx) => idx + 1).map(page => (
            <button
              key={page}
              onClick={() => handlePageChange(page)}
              className={currentPage === page ? 'active' : ''}
              disabled={isLoading}
            >
              {page}
            </button>
          ))}

          <button
            onClick={() => handlePageChange(currentPage + 1)}
            disabled={currentPage === totalPages || isLoading}
          >
            下一页
          </button>
        </div>
      )}
    </div>
  );
};

export default PostList;

关键说明

  • 参数传递:用URLSearchParams构建查询参数,比手动拼接字符串更安全,能自动处理空格、特殊字符的URL编码;
  • 状态联动:搜索关键词变化时重置页码为1,避免用户搜索后停留在无对应数据的页码;
  • 性能优化:后端用Promise.all并行查询数据列表和总条数,减少数据库查询时间;
  • 用户体验:加入加载状态、空数据提示,禁用无效分页按钮,提升交互体验。

内容的提问来源于stack exchange,提问作者user19263124

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 23:45:42