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

Next.js实现加载更多帖子及页面切换数据持久化方案

解决方案:加载更多功能实现 + 页面切换数据不丢失

核心思路

要解决页面切换后数据重置的问题,核心是将已加载的列表数据、当前页码、查询参数等状态从组件内部状态提升到全局状态容器,或者持久化到本地存储,确保页面卸载/切换时状态不会被销毁。页面初始化时优先读取缓存状态,而非直接重置。

方案一:全局状态管理(以React + Redux Toolkit为例)

适合多页面共享数据的场景,状态存储在全局store中,页面切换时状态保留。

1. 定义帖子状态Slice

// features/posts/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchPosts } from '../../api/posts'; // 自定义API请求函数

// 异步获取帖子列表
export const fetchPostsList = createAsyncThunk(
  'posts/fetchPostsList',
  async ({ page, queryParams }, { rejectWithValue }) => {
    try {
      const res = await fetchPosts(page, queryParams);
      return { data: res.data, currentPage: page };
    } catch (err) {
      return rejectWithValue(err.response?.data || '加载失败');
    }
  }
);

const initialState = {
  list: [],          // 已加载的帖子列表
  currentPage: 1,    // 当前页码
  queryParams: {},   // 查询参数(如关键词、分类)
  hasMore: true,     // 是否还有更多数据
  loading: false     // 加载状态
};

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // 更新查询参数并重置状态(搜索条件变化时调用)
    resetSearchState: (state, action) => {
      state.queryParams = action.payload;
      state.list = [];
      state.currentPage = 1;
      state.hasMore = true;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPostsList.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchPostsList.fulfilled, (state, action) => {
        state.loading = false;
        // 合并新数据,而非替换
        state.list = [...state.list, ...action.payload.data];
        state.currentPage = action.payload.currentPage;
        // 根据API返回数据量判断是否还有更多(假设每页10条)
        state.hasMore = action.payload.data.length === 10;
      })
      .addCase(fetchPostsList.rejected, (state) => {
        state.loading = false;
      });
  }
});

export const { resetSearchState } = postsSlice.actions;
export default postsSlice.reducer;

2. 搜索页组件实现

// components/SearchPage.js
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPostsList, resetSearchState } from '../features/posts/postsSlice';
import PostCard from './PostCard';

const SearchPage = () => {
  const dispatch = useDispatch();
  const { list, currentPage, queryParams, hasMore, loading } = useSelector(state => state.posts);

  // 页面初始化:若无缓存数据则加载第一页
  useEffect(() => {
    if (list.length === 0) {
      dispatch(fetchPostsList({ page: 1, queryParams }));
    }
  }, [dispatch, list.length, queryParams]);

  // 加载更多按钮点击事件
  const handleLoadMore = () => {
    if (!loading && hasMore) {
      dispatch(fetchPostsList({ page: currentPage + 1, queryParams }));
    }
  };

  // 搜索条件变化时重置状态并重新加载
  const handleSearch = (newQuery) => {
    dispatch(resetSearchState(newQuery));
    dispatch(fetchPostsList({ page: 1, queryParams: newQuery }));
  };

  return (
    <div className="search-page">
      {/* 搜索栏 */}
      <div className="search-bar">
        <input
          type="text"
          placeholder="输入关键词搜索..."
          value={queryParams.keyword || ''}
          onChange={(e) => handleSearch({ keyword: e.target.value })}
        />
      </div>

      {/* 帖子列表 */}
      <div className="posts-container">
        {list.map(post => (
          <PostCard key={post.id} post={post} />
        ))}
      </div>

      {/* 加载更多按钮 */}
      {hasMore && (
        <button className="load-more-btn" onClick={handleLoadMore} disabled={loading}>
          {loading ? '加载中...' : '加载更多'}
        </button>
      )}
      {!hasMore && <p className="no-more">已加载全部内容</p>}
    </div>
  );
};

export default SearchPage;

方案二:本地存储持久化(localStorage/sessionStorage)

适合单个页面的状态保留,无需全局状态管理,实现简单。

搜索页组件实现

// components/SearchPage.js
import { useState, useEffect } from 'react';
import { fetchPosts } from '../api/posts';
import PostCard from './PostCard';

// 本地存储键名
const STORAGE_KEY = 'search_posts_state';

const SearchPage = () => {
  const [list, setList] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [queryParams, setQueryParams] = useState({});
  const [hasMore, setHasMore] = useState(true);
  const [loading, setLoading] = useState(false);

  // 从localStorage读取缓存状态
  useEffect(() => {
    const savedState = localStorage.getItem(STORAGE_KEY);
    if (savedState) {
      const parsedState = JSON.parse(savedState);
      setList(parsedState.list);
      setCurrentPage(parsedState.currentPage);
      setQueryParams(parsedState.queryParams);
      setHasMore(parsedState.hasMore);
    } else {
      // 首次加载第一页
      loadPosts(1);
    }
  }, []);

  // 状态变化时保存到localStorage
  useEffect(() => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify({
      list,
      currentPage,
      queryParams,
      hasMore
    }));
  }, [list, currentPage, queryParams, hasMore]);

  // 加载帖子的核心函数
  const loadPosts = async (page) => {
    setLoading(true);
    try {
      const res = await fetchPosts(page, queryParams);
      if (page === 1) {
        setList(res.data);
      } else {
        setList(prev => [...prev, ...res.data]);
      }
      setCurrentPage(page);
      setHasMore(res.data.length === 10); // 假设每页10条
    } catch (err) {
      console.error('加载帖子失败:', err);
    } finally {
      setLoading(false);
    }
  };

  // 加载更多逻辑
  const handleLoadMore = () => {
    if (!loading && hasMore) {
      loadPosts(currentPage + 1);
    }
  };

  // 搜索逻辑:重置状态并重新加载
  const handleSearch = (newQuery) => {
    setQueryParams(newQuery);
    setList([]);
    setCurrentPage(1);
    setHasMore(true);
    loadPosts(1);
  };

  return (
    <div className="search-page">
      <div className="search-bar">
        <input
          type="text"
          placeholder="输入关键词搜索..."
          value={queryParams.keyword || ''}
          onChange={(e) => handleSearch({ keyword: e.target.value })}
        />
      </div>

      <div className="posts-container">
        {list.map(post => (
          <PostCard key={post.id} post={post} />
        ))}
      </div>

      {hasMore && (
        <button className="load-more-btn" onClick={handleLoadMore} disabled={loading}>
          {loading ? '加载中...' : '加载更多'}
        </button>
      )}
      {!hasMore && <p className="no-more">已加载全部内容</p>}
    </div>
  );
};

export default SearchPage;

注意事项

  • 全局状态管理适合多页面共享数据的场景,本地存储适合单个页面的轻量持久化,注意localStorage最大存储容量为5MB。
  • 当搜索条件变化时,必须重置列表、页码等状态,避免新旧搜索结果混合。
  • 使用本地存储时,可在用户退出搜索页或清除缓存时手动删除存储数据,避免冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 03:45:03