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

React中useState与useMemo在客户端路由时状态丢失问题求助

问题原因分析

你的问题核心在于:客户端路由切换时,Home 组件会被卸载并重新挂载。此时 useState 会回到初始的空数组状态,而依赖空数组的 useMemo(或者你本应使用的 useEffect)会重新执行,生成新的随机排序结果,导致 sortedQuestions 内容变化。

另外要注意:useMemo 的设计目的是缓存计算结果,不是用来执行副作用(比如调用 setState),这里应该换成 useEffect 才符合 React 的设计规范。

解决方案

方案一:状态提升到全局父组件

将 sortedQuestions 的状态托管到不会随路由卸载的父组件(比如 Next.js 的 _app.tsx),通过 props 传递给 Home 组件,确保路由切换时状态不丢失。

代码示例:

// _app.tsx
import { useState, useEffect } from 'react';
import type { AppProps } from 'next/app';
import { QnaTypes, qna } from '../lib/qna';
import { getRandomSort } from '../lib/random';

export default function App({ Component, pageProps }: AppProps) {
  const [sortedQuestions, setSortedQuestions] = useState<QnaTypes[]>([]);

  useEffect(() => {
    // 仅在初始状态为空时生成排序结果
    if (sortedQuestions.length === 0) {
      const sortedQna = qna.slice(0, 15).sort(getRandomSort);
      setSortedQuestions(sortedQna);
    }
  }, [sortedQuestions]);

  return <Component {...pageProps} sortedQuestions={sortedQuestions} />;
}
// Home.tsx
import QuestionCard from "../components/ui/question-card";
import { QnaTypes } from "../lib/qna";

type HomeProps = {
  sortedQuestions: QnaTypes[];
};

export default function Home({ sortedQuestions }: HomeProps) {
  return (
    <div className="p-4">
      <h1 className="g-text font-extrabold text-3xl text-center pb-6">Explore questions to practice</h1>
      <div className="flex justify-between flex-wrap items-start gap-3">
        {sortedQuestions.map((question) => (
          <QuestionCard question={question.question} answer={question.answer} id={question.id} key={question.id} />
        ))}
      </div>
    </div>
  );
}

方案二:LocalStorage 持久化状态

将排序后的结果存储到 localStorage,组件重新挂载时优先读取本地存储的数据,没有的话再生成新结果。这种方式即使页面刷新也能保留状态。

代码示例:

import { useState, useEffect } from "react";
import QuestionCard from "../components/ui/question-card";
import { QnaTypes, qna } from "../lib/qna";
import { getRandomSort } from "../lib/random";

export default function Home() {
  const [sortedQuestions, setSortedQuestions] = useState<QnaTypes[]>([]);

  useEffect(() => {
    const savedQuestions = localStorage.getItem('sortedQuestions');
    if (savedQuestions) {
      // 读取本地存储的排序结果
      setSortedQuestions(JSON.parse(savedQuestions));
    } else {
      // 生成新结果并保存到本地
      const sortedQna = qna.slice(0, 15).sort(getRandomSort);
      setSortedQuestions(sortedQna);
      localStorage.setItem('sortedQuestions', JSON.stringify(sortedQna));
    }
  }, []);

  return (
    <div className="p-4">
      <h1 className="g-text font-extrabold text-3xl text-center pb-6">Explore questions to practice</h1>
      <div className="flex justify-between flex-wrap items-start gap-3">
        {sortedQuestions.map((question) => (
          <QuestionCard question={question.question} answer={question.answer} id={question.id} key={question.id} />
        ))}
      </div>
    </div>
  );
}

注意:如果你的 qna 数据源会更新,建议给存储的数据加版本标识,避免读取旧的过期数据。

方案三:React Context 全局状态管理

如果多个组件需要访问这个排序后的状态,可以用 React Context 封装,实现全局状态共享,路由切换时状态不会丢失。

代码示例:

// contexts/QuestionContext.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { QnaTypes, qna } from '../lib/qna';
import { getRandomSort } from '../lib/random';

type QuestionContextType = {
  sortedQuestions: QnaTypes[];
};

const QuestionContext = createContext<QuestionContextType | undefined>(undefined);

export function QuestionProvider({ children }: { children: ReactNode }) {
  const [sortedQuestions, setSortedQuestions] = useState<QnaTypes[]>([]);

  useEffect(() => {
    if (sortedQuestions.length === 0) {
      const sortedQna = qna.slice(0, 15).sort(getRandomSort);
      setSortedQuestions(sortedQna);
    }
  }, [sortedQuestions]);

  return (
    <QuestionContext.Provider value={{ sortedQuestions }}>
      {children}
    </QuestionContext.Provider>
  );
}

export function useQuestion() {
  const context = useContext(QuestionContext);
  if (!context) {
    throw new Error('useQuestion must be used within a QuestionProvider');
  }
  return context;
}
// _app.tsx
import { QuestionProvider } from '../contexts/QuestionContext';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <QuestionProvider>
      <Component {...pageProps} />
    </QuestionProvider>
  );
}
// Home.tsx
import QuestionCard from "../components/ui/question-card";
import { useQuestion } from "../contexts/QuestionContext";

export default function Home() {
  const { sortedQuestions } = useQuestion();

  return (
    <div className="p-4">
      <h1 className="g-text font-extrabold text-3xl text-center pb-6">Explore questions to practice</h1>
      <div className="flex justify-between flex-wrap items-start gap-3">
        {sortedQuestions.map((question) => (
          <QuestionCard question={question.question} answer={question.answer} id={question.id} key={question.id} />
        ))}
      </div>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:42:45