NextJS:解决服务端传组织列表至客户端组件的陈旧数据问题
背景
我有一个Server Component负责获取组织列表并传递给Client Component。Client Component通过props初始化organizations状态,之后会执行修改组织状态(比如从活跃改为归档)的操作。操作后能正常显示更新后的数组,但组件重新挂载时,会使用最初通过props传递的陈旧数据——原因是props未发生变化,状态被重新初始化了。
ServerComponent代码
export default async function ServerComponent({searchParams}) { const pageNumber = !!searchParams.page ? searchParams.page : 1; const organizations = await getOrganizations(session?.user.email, pageNumber); return <Organizations initialOrganizations={organizations}/> }
ClientComponent代码
export default function Organizations({ initialOrganizations = [] }) { const [organizations, setOrganizations] = useState(initialOrganizations); const [selectedOrganization, setSelectedOrganization] = useState({}); const [toggle, setToggle] = useState(true); const [tabs, setTabs] = useState([{ name: 'Active', current: true }, { name: 'Archive', current: false }]); const [activeTab, setActiveTab] = useState('Active'); const router = useRouter(); const search = useSearchParams(); useEffect(() => { const page = search.get('page'); if(page) { router.push(`/portal/organizations?page=${page}`) } }, [search]) const handleOrganizationUpdate = (updatedOrganization) => { setOrganizations((prevOrganizations) => prevOrganizations.map((o) => o.id === updatedOrganization.id ? updatedOrganization : o ) ); }; const archiveOrganization = async (organizationId) => { const response = await fetch('/api/organization/archive',{ method: 'POST', headers: { "Content-Type": "application/json", }, body: JSON.stringify({ organizationId }) }); if(response.ok) { const responseBody = await response.json(); const updatedOrganization = responseBody.response; console.log(updatedOrganization) setOrganizations((prev) => prev.map((organization) => organization.id === updatedOrganization.id ? { ...organization, status: updatedOrganization.status } : organization ) ); } } return ( toggle ? <> <Button variant='solid' href='/portal/organizations/create-organization'> <span>Create Organization</span> </Button> <Tab tabs={tabs} setTabs={setTabs} setActiveTab={setActiveTab}/> {organizations.map((organization) => ( activeTab.toUpperCase() == organization.status && <div key={organization.id} className="group flex justify-between mt-10 flex-row items-center hover:bg-slate-900 w-3/5 px-8 py-6 mb-5 border-b-cyan-50 rounded shadow-black hover:shadow-black shadow-md hover:shadow-lg" > <div className='flex flex-col'> <h2 className='text-xs text-slate-300'>Organization Name</h2> <h2 onClick={() => {setToggle(false); setSelectedOrganization(organization)}} className="text-base hover:underline">{organization.name}</h2> </div> <Button onClick={() => {archiveOrganization(organization.id)}} className="opacity-0 transition-opacity duration-100 ease-in-out group-hover:opacity-100" variant="solid"> {organization.status == "ACTIVE" ? <span>Archive</span> : <span>Active</span>} </Button> </div> ))} </> : <Organization organization={selectedOrganization} handleOrganizationUpdate={handleOrganizationUpdate} setToggle={setToggle}/> ); }
方案一:导航到/organizations时服务端重新获取数据
Next.js默认会缓存Server Component的渲染结果,导致用户重新进入页面时不会再次调用getOrganizations获取最新数据。你可以通过以下方式强制每次导航都重新拉取:
方式1:禁用缓存(Next.js 13+ App Router)
使用noStore禁用组件缓存,确保每次请求都重新获取数据:
import { noStore } from 'next/cache'; export default async function ServerComponent({searchParams}) { noStore(); // 禁用缓存,每次请求都重新获取数据 const pageNumber = !!searchParams.page ? searchParams.page : 1; const organizations = await getOrganizations(session?.user.email, pageNumber); return <Organizations initialOrganizations={organizations}/> }
方式2:设置实时验证
通过revalidate=0让组件每次请求都重新验证数据:
export const revalidate = 0; // 每次请求都重新验证数据 export default async function ServerComponent({searchParams}) { const pageNumber = !!searchParams.page ? searchParams.page : 1; const organizations = await getOrganizations(session?.user.email, pageNumber); return <Organizations initialOrganizations={organizations}/> }
两种方式都能保证用户每次进入/organizations页面时,Server Component都会拉取最新的组织列表,Client Component拿到的initialOrganizations就是最新数据,重新挂载时状态会用最新值初始化。
方案二:在Client Component中避免使用陈旧数据
如果不想每次都从服务端拉取数据,可以在Client Component里监听props的变化,确保状态始终和最新的props同步:
修改ClientComponent的状态同步逻辑
添加useEffect监听initialOrganizations的变化,当props更新时自动同步到状态:
export default function Organizations({ initialOrganizations = [] }) { const [organizations, setOrganizations] = useState(initialOrganizations); // ...其他状态保持不变 // 监听initialOrganizations变化,同步到状态 useEffect(() => { setOrganizations(initialOrganizations); }, [initialOrganizations]); // 依赖项为initialOrganizations,props变化时触发 // ...其余逻辑保持不变 }
进阶:结合路由监听刷新状态
如果希望保留客户端操作后的状态,但在用户从其他页面返回时用最新的服务端数据,可以结合usePathname监听路由变化:
import { usePathname } from 'next/navigation'; export default function Organizations({ initialOrganizations = [] }) { const [organizations, setOrganizations] = useState(initialOrganizations); const pathname = usePathname(); // 当路由回到/organizations时,用最新的initialOrganizations更新状态 useEffect(() => { setOrganizations(initialOrganizations); }, [pathname, initialOrganizations]); // ...其余逻辑保持不变 }
这样用户从其他页面导航回来时,路由变化会触发useEffect,将最新的props数据同步到状态中,避免使用陈旧数据。
内容的提问来源于stack exchange,提问作者Aamir Khan

