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
相关产品推荐
相关产品推荐

