Next.js中如何更新layout.tsx中非页面组件的数据?
问题:Next.js中更新Layout组件内的侧边栏项目列表
我正在学习Next.js,用Firebase开发一个简单的项目管理应用。我知道向数据库添加数据后可以用revalidatePath获取更新后的数据集,但不清楚怎么处理数据展示在layout.tsx组件(而非页面)里的场景。
具体场景:
- 侧边栏展示当前登录用户的所有项目,该侧边栏属于
layout.tsx,不属于单个页面 - 有一个创建项目的表单页面,创建项目后需要更新侧边栏的项目列表
现有服务端代码
创建项目的服务端操作
"use server"; import { Project } from "@/app/models/Project"; import { db } from "@/config/firebase"; import { addDoc, collection } from "firebase/firestore"; export const CreateProject = async (formData: { name: string; color: string }, user: string) => { try { const collectionRef = collection(db, "projects"); const projectRef = await addDoc(collectionRef, { name: formData.name, color: formData.color, user, }); const project = { id: projectRef.id, name: formData.name, color: formData.color, user, }; return project; } catch (error) { console.log("Error adding document: ", error); } };
获取项目的服务端操作
"use server"; import { Project } from "@/app/models/Project"; import { db } from "@/config/firebase"; import { collection, getDocs, query, where } from "firebase/firestore"; export const GetProjects = async (user: string) => { try { const collectionRef = collection(db, "projects"); const q = query(collectionRef, where("user", "==", user)); const querySnapshot = await getDocs(q); const projects: Project[] = querySnapshot.docs.map((doc) => ({ id: doc.id, name: doc.data().name, color: doc.data().color, user: doc.data().user, })); return projects; } catch (error) { console.log("Error getting documents: ", error); } };
解决方案
方法1:使用revalidateTag触发布局数据刷新
因为layout.tsx是全局布局,不属于单个页面路径,用缓存标签替代路径验证更合适。
修改创建项目函数,添加标签验证
在CreateProject中导入revalidateTag,创建项目后验证对应用户的项目标签:"use server"; import { Project } from "@/app/models/Project"; import { db } from "@/config/firebase"; import { addDoc, collection } from "firebase/firestore"; import { revalidateTag } from "next/cache"; // 导入revalidateTag export const CreateProject = async (formData: { name: string; color: string }, user: string) => { try { const collectionRef = collection(db, "projects"); const projectRef = await addDoc(collectionRef, { name: formData.name, color: formData.color, user, }); const project = { id: projectRef.id, name: formData.name, color: formData.color, user, }; // 验证用户专属的项目标签,触发布局刷新 revalidateTag(`projects-${user}`); return project; } catch (error) { console.log("Error adding document: ", error); } };在Layout中给数据请求绑定缓存标签
使用unstable_cache包裹GetProjects调用,关联对应的标签:// layout.tsx(Server Component) import { GetProjects } from "@/app/actions/projects"; import { getCurrentUser } from "@/app/actions/auth"; import { unstable_cache } from "next/cache"; export default async function RootLayout({ children }: { children: React.ReactNode }) { const user = await getCurrentUser(); // 绑定缓存标签,与创建时的标签对应 const getCachedProjects = unstable_cache( async (userId) => GetProjects(userId), [`projects-${user.id}`], { tags: [`projects-${user.id}`] } ); const projects = await getCachedProjects(user.id); return ( <html lang="zh-CN"> <body> <Sidebar projects={projects} /> {children} </body> </html> ); }
方法2:客户端组件+Firestore实时监听
如果需要即时更新,把侧边栏改成客户端组件,用Firestore的实时快照监听数据变化:
将侧边栏改为客户端组件
// components/Sidebar.tsx 'use client'; import { useEffect, useState } from 'react'; import { db } from '@/config/firebase'; import { collection, query, where, onSnapshot } from 'firebase/firestore'; import { Project } from '@/app/models/Project'; export default function Sidebar({ userId }: { userId: string }) { const [projects, setProjects] = useState<Project[]>([]); useEffect(() => { // 创建查询并监听数据变化 const q = query(collection(db, 'projects'), where('user', '==', userId)); const unsubscribe = onSnapshot(q, (querySnapshot) => { const updatedProjects = querySnapshot.docs.map(doc => ({ id: doc.id, name: doc.data().name, color: doc.data().color, user: doc.data().user, })); setProjects(updatedProjects); }); // 组件卸载时取消监听 return () => unsubscribe(); }, [userId]); return ( <div className="sidebar"> <h3>我的项目</h3> {projects.map(project => ( <div key={project.id} style={{ color: project.color }}> {project.name} </div> ))} </div> ); }在Layout中传递用户ID
// layout.tsx(Server Component) import Sidebar from '@/components/Sidebar'; import { getCurrentUser } from '@/app/actions/auth'; export default async function RootLayout({ children }: { children: React.ReactNode }) { const user = await getCurrentUser(); return ( <html lang="zh-CN"> <body> <Sidebar userId={user.id} /> {children} </body> </html> ); }这种方式无需手动触发刷新,Firestore会自动推送数据更新到客户端。
方法3:重定向触发页面刷新
如果不需要实时更新,创建项目后重定向到任意页面,会触发Layout重新渲染并获取最新数据:
"use server"; import { Project } from "@/app/models/Project"; import { db } from "@/config/firebase"; import { addDoc, collection } from "firebase/firestore"; import { redirect } from "next/navigation"; export const CreateProject = async (formData: { name: string; color: string }, user: string) => { try { const collectionRef = collection(db, "projects"); await addDoc(collectionRef, { name: formData.name, color: formData.color, user, }); // 重定向到项目列表页或表单页,触发Layout刷新 redirect('/projects'); } catch (error) { console.log("Error adding document: ", error); } };
内容的提问来源于stack exchange,提问作者m_novak
相关产品推荐
相关产品推荐

