Skip to content

第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/settings

5.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 的页面与组件基础,包括页面组件编写、布局组件使用、组件开发、静态资源处理和元数据配置等内容。通过本章的学习,你应该已经掌握了:

  1. 页面组件的编写:包括服务端渲染和客户端组件
  2. 布局组件的使用:根布局和局部布局
  3. 组件开发:自定义组件、props 传递和事件 emit
  4. 组件自动导入:Next.js 的内置特性
  5. 静态资源处理:图片、字体和样式文件
  6. 元数据配置:静态和动态元数据,以及 SEO 优化

Next.js 的页面与组件系统是其核心功能之一,它提供了简洁、直观的方式来构建用户界面。在实际开发中,建议使用 App Router 和服务端组件,它们提供了更好的性能和开发体验。

接下来,我们将学习 Next.js 14 的核心特性:Server Components 与 Client Components,这是 Next.js 14 的重要创新。

© 2026 编程马·菜鸟教程 版权所有