Appearance
第5章:页面与组件基础
5.1 页面组件编写(page.js 基础结构、渲染逻辑)
页面组件基础结构
页面组件是 Next.js 应用中的核心组件,用于定义页面内容。在 App Router 中,页面组件通过 page.js 文件定义。
基本结构:
jsx
// app/page.js
export default function Home() {
return (
<div>
<h1>Home Page</h1>
<p>Welcome to my Next.js app!</p>
</div>
);
}服务端渲染
在 App Router 中,页面组件默认在服务端渲染,这意味着它们可以直接使用 async/await 获取数据。
示例:
jsx
// app/blog/page.js
export default async function BlogPage() {
// 直接在服务端获取数据
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}客户端组件
如果页面需要客户端交互,可以使用 'use client' 指令将其标记为客户端组件。
示例:
jsx
// app/counter/page.js
'use client';
import { useState } from 'react';
export default function CounterPage() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Counter</h1>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}5.2 布局组件(layout.js)使用(全局布局、局部布局切换)
根布局
根布局是应用的最外层布局,定义在 app/layout.js 文件中。
示例:
jsx
// app/layout.js
import './globals.css';
export const metadata = {
title: 'My Next.js App',
description: 'A Next.js app with App Router',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
<main>{children}</main>
<footer>
<p>© 2024 My Next.js App</p>
</footer>
</body>
</html>
);
}局部布局
局部布局应用于特定目录及其子目录,定义在相应目录的 layout.js 文件中。
示例:
jsx
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="blog-layout">
<aside>
<h2>Blog Categories</h2>
<ul>
<li><a href="/blog?category=tech">Technology</a></li>
<li><a href="/blog?category=travel">Travel</a></li>
<li><a href="/blog?category=food">Food</a></li>
</ul>
</aside>
<main>{children}</main>
</div>
);
}布局嵌套
布局可以嵌套使用,子布局会继承父布局的结构。
示例:
app/
├── layout.js # 根布局
├── dashboard/
│ ├── layout.js # 仪表盘布局
│ ├── page.js # /dashboard
│ └── settings/
│ ├── layout.js # 设置布局
│ └── page.js # /dashboard/settings5.3 组件开发(自定义组件、props 传递、emit 事件)
自定义组件
创建可复用的 React 组件,放在 components 目录中。
示例:
jsx
// components/Button.jsx
import React from 'react';
const Button = ({ children, onClick, variant = 'primary' }) => {
const classes = `btn btn-${variant}`;
return (
<button className={classes} onClick={onClick}>
{children}
</button>
);
};
export default Button;Props 传递
通过 props 向组件传递数据和函数。
示例:
jsx
// app/page.js
import Button from '@/components/Button';
export default function Home() {
const handleClick = () => {
console.log('Button clicked!');
};
return (
<div>
<h1>Home Page</h1>
<Button onClick={handleClick}>Click Me</Button>
<Button variant="secondary" onClick={handleClick}>Secondary Button</Button>
</div>
);
}Emit 事件
在客户端组件中,可以使用回调函数向父组件传递事件。
示例:
jsx
// components/Form.jsx
'use client';
import { useState } from 'react';
const Form = ({ onSubmit }) => {
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ name });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<button type="submit">Submit</button>
</form>
);
};
export default Form;使用:
jsx
// app/contact/page.js
'use client';
import { useState } from 'react';
import Form from '@/components/Form';
export default function ContactPage() {
const [submitted, setSubmitted] = useState(false);
const handleSubmit = (data) => {
console.log('Form submitted:', data);
setSubmitted(true);
};
return (
<div>
<h1>Contact Page</h1>
{submitted ? (
<p>Thank you for your submission!</p>
) : (
<Form onSubmit={handleSubmit} />
)}
</div>
);
}5.4 组件自动导入与全局注册(Next.js 内置特性)
Next.js 13+ 支持组件自动导入,无需手动导入组件。
配置自动导入
在 next.config.js 中配置组件自动导入:
javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
// 启用组件自动导入
typedRoutes: true,
},
};
module.exports = nextConfig;自动导入组件
在 components 目录中创建的组件会自动导入,无需手动导入。
示例:
jsx
// app/page.js
// 无需手动导入 Button 组件
export default function Home() {
return (
<div>
<h1>Home Page</h1>
<Button>Click Me</Button>
</div>
);
}全局组件
可以将组件放在 components/ui 目录中,作为全局组件使用。
5.5 静态资源处理(图片、字体、样式文件存放规则与引用方式)
图片处理
Next.js 提供了内置的 Image 组件,用于优化图片加载。
Image 组件使用
jsx
// app/page.js
import Image from 'next/image';
import profilePic from '@/public/profile.jpg';
export default function Home() {
return (
<div>
<h1>Home Page</h1>
{/* 使用本地图片 */}
<Image
src={profilePic}
alt="Profile picture"
width={200}
height={200}
/>
{/* 使用外部图片 */}
<Image
src="https://example.com/image.jpg"
alt="External image"
width={400}
height={300}
/>
</div>
);
}图片优化特性
- 自动格式转换:将图片转换为现代格式(如 WebP)
- 响应式图片:根据设备尺寸提供合适大小的图片
- 懒加载:图片在进入视口时才加载
- 预加载:关键图片可以预加载
字体文件导入与配置
Next.js 提供了 next/font 模块,用于优化字体加载。
使用本地字体
jsx
// app/layout.js
import { Inter } from 'next/font/local';
import './globals.css';
// 导入本地字体
const inter = Inter({
src: './fonts/Inter-Regular.ttf',
weight: '400',
subsets: ['latin'],
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}使用 Google 字体
jsx
// app/layout.js
import { Inter } from 'next/font/google';
import './globals.css';
// 导入 Google 字体
const inter = Inter({
subsets: ['latin'],
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}样式文件处理
全局样式
在 app/globals.css 中定义全局样式:
css
/* app/globals.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background-color: #0070f3;
color: white;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}组件样式
使用 CSS 模块为组件添加样式:
css
/* components/Button.module.css */
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary {
background-color: #0070f3;
color: white;
}
.secondary {
background-color: #6c757d;
color: white;
}使用:
jsx
// components/Button.jsx
import styles from './Button.module.css';
const Button = ({ children, variant = 'primary' }) => {
return (
<button className={`${styles.btn} ${styles[variant]}`}>
{children}
</button>
);
};
export default Button;5.6 元数据配置(metadata 对象、generateMetadata 函数,优化 SEO)
静态元数据
在布局或页面组件中使用 metadata 对象定义静态元数据:
jsx
// app/layout.js
export const metadata = {
title: 'My Next.js App',
description: 'A Next.js app with App Router',
keywords: 'Next.js, React, JavaScript',
author: 'John Doe',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}动态元数据
在页面组件中使用 generateMetadata 函数定义动态元数据:
jsx
// app/blog/[id]/page.js
import { notFound } from 'next/navigation';
export async function generateMetadata({ params }) {
const { id } = params;
// 获取文章数据
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
if (!post) {
notFound();
}
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.featuredImage],
},
};
}
export default async function BlogPost({ params }) {
const { id } = params;
const response = await fetch(`https://api.example.com/posts/${id}`);
const post = await response.json();
if (!post) {
notFound();
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}SEO 优化
- 标题和描述:为每个页面设置独特的标题和描述
- Open Graph 标签:优化社交媒体分享
- 结构化数据:添加 Schema.org 结构化数据
- ** canonical 链接**:避免重复内容
示例:
jsx
// app/page.js
export const metadata = {
title: 'Home | My Next.js App',
description: 'Welcome to my Next.js app, where you can find the latest news and articles.',
openGraph: {
title: 'Home | My Next.js App',
description: 'Welcome to my Next.js app, where you can find the latest news and articles.',
url: 'https://example.com',
siteName: 'My Next.js App',
images: [
{
url: 'https://example.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'My Next.js App',
},
],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Home | My Next.js App',
description: 'Welcome to my Next.js app, where you can find the latest news and articles.',
images: ['https://example.com/og-image.jpg'],
},
};
export default function Home() {
return (
<div>
<h1>Home Page</h1>
<p>Welcome to my Next.js app!</p>
</div>
);
}小结
本章介绍了 Next.js 的页面与组件基础,包括页面组件编写、布局组件使用、组件开发、静态资源处理和元数据配置等内容。通过本章的学习,你应该已经掌握了:
- 页面组件的编写:包括服务端渲染和客户端组件
- 布局组件的使用:根布局和局部布局
- 组件开发:自定义组件、props 传递和事件 emit
- 组件自动导入:Next.js 的内置特性
- 静态资源处理:图片、字体和样式文件
- 元数据配置:静态和动态元数据,以及 SEO 优化
Next.js 的页面与组件系统是其核心功能之一,它提供了简洁、直观的方式来构建用户界面。在实际开发中,建议使用 App Router 和服务端组件,它们提供了更好的性能和开发体验。
接下来,我们将学习 Next.js 14 的核心特性:Server Components 与 Client Components,这是 Next.js 14 的重要创新。
