Next.js模板与默认组件 #
一、模板 template.tsx #
1.1 模板概念 #
模板与布局类似,都会包裹子组件,但模板在导航时会重新创建实例:
| 特性 | 布局 Layout | 模板 Template |
|---|---|---|
| 状态保持 | 保持 | 不保持 |
| 重新渲染 | 否 | 是 |
| 实例创建 | 一次 | 每次导航 |
| 性能 | 更好 | 稍差 |
1.2 基本用法 #
tsx
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return <div>{children}</div>
}
1.3 模板与布局的区别 #
布局示例
tsx
export default function Layout({
children,
}: {
children: React.ReactNode
}) {
console.log('Layout rendered')
return <div className="layout">{children}</div>
}
模板示例
tsx
export default function Template({
children,
}: {
children: React.ReactNode
}) {
console.log('Template rendered')
return <div className="template">{children}</div>
}
1.4 使用场景 #
进入/退出动画
tsx
'use client'
import { motion } from 'framer-motion'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
)
}
页面访问统计
tsx
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
useEffect(() => {
console.log('Page viewed:', pathname)
}, [pathname])
return <div>{children}</div>
}
页面加载效果
tsx
'use client'
import { useState, useEffect } from 'react'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(false)
}, [])
if (loading) {
return <div className="loading">加载中...</div>
}
return <div>{children}</div>
}
1.5 模板与布局组合 #
text
app/
├── layout.tsx
├── template.tsx
└── page.tsx
渲染顺序:
text
RootLayout
└── Template
└── Page
tsx
export default function Layout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="layout">
<header>Header</header>
{children}
</div>
)
}
tsx
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="template">
{children}
</div>
)
}
二、默认组件 default.tsx #
2.1 默认组件概念 #
默认组件用于并行路由的备用UI,当插槽没有匹配的页面时显示:
text
app/
└── dashboard/
├── @team/
│ └── page.tsx
├── @analytics/
│ └── default.tsx # 默认组件
└── layout.tsx
2.2 基本用法 #
tsx
export default function Default() {
return <div>暂无内容</div>
}
2.3 并行路由默认页 #
text
app/
└── dashboard/
├── @team/
│ ├── page.tsx
│ └── default.tsx
├── @analytics/
│ ├── page.tsx
│ └── default.tsx
├── layout.tsx
└── page.tsx
布局
tsx
interface LayoutProps {
team: React.ReactNode
analytics: React.ReactNode
}
export default function DashboardLayout({
team,
analytics,
}: LayoutProps) {
return (
<div className="grid grid-cols-2 gap-4">
<div>{team}</div>
<div>{analytics}</div>
</div>
)
}
默认组件
tsx
export default function DefaultTeam() {
return (
<div className="p-4 text-gray-500">
选择一个团队查看详情
</div>
)
}
2.4 使用场景 #
空状态显示
tsx
export default function Default() {
return (
<div className="flex flex-col items-center justify-center h-64 text-gray-400">
<svg className="w-16 h-16 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
<p>暂无数据</p>
</div>
)
}
加载占位
tsx
export default function Default() {
return (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
)
}
引导用户操作
tsx
export default function Default() {
return (
<div className="text-center py-8">
<h3 className="text-lg font-medium mb-2">开始使用</h3>
<p className="text-gray-500 mb-4">创建您的第一个项目</p>
<button className="px-4 py-2 bg-blue-500 text-white rounded">
创建项目
</button>
</div>
)
}
三、综合示例 #
3.1 带动画的仪表盘 #
text
app/
└── dashboard/
├── layout.tsx
├── template.tsx
├── @sidebar/
│ ├── page.tsx
│ └── default.tsx
├── @content/
│ ├── page.tsx
│ └── default.tsx
└── page.tsx
布局
tsx
interface LayoutProps {
sidebar: React.ReactNode
content: React.ReactNode
}
export default function DashboardLayout({ sidebar, content }: LayoutProps) {
return (
<div className="flex min-h-screen">
<aside className="w-64 bg-gray-800 text-white">
{sidebar}
</aside>
<main className="flex-1 p-6">
{content}
</main>
</div>
)
}
模板
tsx
'use client'
import { motion } from 'framer-motion'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
)
}
默认侧边栏
tsx
export default function DefaultSidebar() {
return (
<nav className="p-4">
<ul className="space-y-2">
<li><a href="/dashboard">概览</a></li>
<li><a href="/dashboard/analytics">分析</a></li>
<li><a href="/dashboard/settings">设置</a></li>
</ul>
</nav>
)
}
默认内容
tsx
export default function DefaultContent() {
return (
<div className="flex items-center justify-center h-full">
<p className="text-gray-500">选择一个菜单项查看详情</p>
</div>
)
}
3.2 页面切换动画 #
tsx
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { usePathname } from 'next/navigation'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
return (
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
</AnimatePresence>
)
}
3.3 页面访问追踪 #
tsx
'use client'
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
const url = pathname + (searchParams.toString() ? `?${searchParams}` : '')
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: url,
})
}
}, [pathname, searchParams])
return <>{children}</>
}
四、最佳实践 #
4.1 何时使用模板 #
- 需要页面切换动画
- 需要追踪页面访问
- 需要每次导航时重置状态
- 需要进入/退出效果
4.2 何时使用默认组件 #
- 并行路由的备用UI
- 空状态显示
- 加载占位符
- 引导用户操作
4.3 性能考虑 #
tsx
'use client'
import { lazy, Suspense } from 'react'
const HeavyAnimation = lazy(() => import('./HeavyAnimation'))
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return (
<Suspense fallback={<>{children}</>}>
<HeavyAnimation>
{children}
</HeavyAnimation>
</Suspense>
)
}
五、总结 #
模板与默认组件要点:
| 特性 | 模板 | 默认组件 |
|---|---|---|
| 文件 | template.tsx | default.tsx |
| 用途 | 页面包裹 | 并行路由备用 |
| 状态 | 每次重新创建 | - |
| 场景 | 动画、追踪 | 空状态、占位 |
下一步,让我们学习元数据与SEO!
最后更新:2026-03-28