1. 概念
Next.js 是一款基于 React 的 Web 开发框架。React 是一个用于构建用户界面的 JavaScript 库,而 Next.js 则在 React 的基础上提供了更多功能,如服务器端渲染(SSR)、静态生成(SSG)等,帮助开发者更高效地构建 Web 应用。
2. 项目搭建
搭建 Next.js 项目的脚手架命令如下:
npx create-next-app@latest
运行命令后,你会看到以下配置选项:
- 项目名称:
What is your project named?
- 是否使用 TypeScript:
Would you like to use TypeScript?
(推荐使用,有助于类型检查和代码提示) - 代码规范工具:
Which linter would you like to use?
(推荐使用 ESLint,用于检查代码规范) - 是否使用 Tailwind CSS:
Would you like to use Tailwind CSS?
(推荐使用,方便快速开发响应式布局) - 代码是否放在
src/
目录:Would you like your code inside a
src/directory?
(推荐放在src/
目录,有助于项目结构清晰) - 是否使用 App Router:
Would you like to use App Router?
(推荐使用,这是 Next.js 的新一代路由系统) - 是否使用 Turbopack:
Would you like to use Turbopack?
(推荐使用,这是一个新的打包工具,可以提高构建速度)
3. 项目结构
Next.js 的项目结构与 Vue 类似。在第一次运行开发服务器时,你可能需要对 layout.tsx
文件进行一些调整。例如,如果你在国内,可能需要注释掉以下代码,因为这些是谷歌的字体,没有 VPN 的情况下加载会很慢:
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
注释掉 ${geistSans.variable} ${geistMono.variable}
部分即可。
4. 组件
组件是可复用的代码块,通常是一个函数。以 page.tsx
为例,function Home
里的 return
返回的是一个 HTML 模板。
你可以在同一个文件中创建自己的组件,例如一个按钮组件:
function Button() {
return (
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
Click Me
</button>
);
}
然后在页面的其他地方通过 <Button />
来使用它。你也可以在 components
文件夹中新增 .tsx
文件来处理组件,但要注意添加 export default
,并在需要使用它的页面中通过 import
导入。
你还可以将页面中的 main
和 footer
内容单独提取为组件,如 MyMain
和 MyFooter
,以便复用。
父子组件之间可以传递参数。例如,MyButton
组件可以接受一个 children
参数:
export default function Button({ children }: { children: React.ReactNode }) {
return (
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
{children}
</button>
);
}
使用时:
<MyButton><b>我很帅</b></MyButton>
你也可以传递一个名为 title
的参数:
function MyMain({ title }: { title: string }) {
return <div>{title}</div>;
}
使用时:
<MyMain title="hello world" />
在组件的 return
之前,你可以定义自己的逻辑。例如:
function MyMain({ title }: { title: string }) {
const newTitle = title + '1234';
return <div>{newTitle}</div>;
}
若要在项目中引入远端的线上图片,可以在 next.config.js
中配置如下内容:
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'www.baidu.com',
},
],
},
};
5. 路由
动态路由
动态路由是 Next.js 中一个非常强大的功能,它允许你根据 URL 的参数动态地加载页面内容。这对于构建如博客、电商网站等需要根据 ID 或其他参数显示不同内容的页面非常有用。
什么是动态路由?
动态路由允许你定义一个页面模板,该模板可以根据 URL 中的参数动态加载不同的内容。例如,你有一个博客网站,每个博客文章都有一个唯一的 ID。你可以使用动态路由来创建一个页面模板,该模板可以根据文章 ID 显示不同的文章内容。
示例
假设你有一个博客文章页面,每个文章都有一个唯一的 ID。你可以这样设置动态路由:
创建动态路由文件夹:在
app
文件夹下创建一个名为[id]
文件夹(文件夹就叫[id]
这个名字),其中id
是动态参数的名称,如1、2、3、4等。创建页面文件:在
[id]
文件夹下创建一个page.tsx
文件。这个文件将作为动态页面的模板。
// app/[id]/page.tsx
import { notFound } from 'next/navigation';
export default async function PostPage({ params }: { params: { id: string } }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
if (!res.ok) {
notFound(); // 如果请求失败,返回 404 页面
}
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
在这个例子中,params.id
是从 URL 中提取的动态参数。当用户访问 /1
、/2
等路径时,Next.js 会自动将 1
、2
等值传递给 params.id
,然后根据这个 ID 获取相应的文章内容。
每个组件的 layout
layout
是 Next.js 中用于定义页面布局的组件。它允许你为一组页面共享相同的布局结构,例如头部、底部、侧边栏等。每个文件夹可以有自己的 layout
,这些 layout
会嵌套在一起,形成一个完整的页面结构。
什么是 layout
?
layout
是一个 React 组件,它定义了页面的共享结构。你可以在这个组件中放置导航栏、底部栏等公共部分,然后通过 children
属性嵌入具体的页面内容。
示例
假设你有一个博客应用,每个页面都有一个公共的头部和底部。你可以这样设置 layout
:
- 创建根
layout
:在app
文件夹下创建一个layout.tsx
文件。这个文件定义了整个应用的根布局。
// app/layout.tsx
import React from 'react';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main>{children}</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
);
}
- 创建子文件夹的
layout
:在子文件夹下创建自己的layout
文件。这些layout
会嵌套在根layout
中。
// app/blog/layout.tsx
import React from 'react';
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<section>
<h1>Blog</h1>
{children}
</section>
);
}
- 创建页面文件:在子文件夹下创建具体的页面文件。这些页面文件会自动嵌套在父文件夹的
layout
中。
// app/blog/[id]/page.tsx
import { notFound } from 'next/navigation';
export default async function PostPage({ params }: { params: { id: string } }) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
if (!res.ok) {
notFound();
}
const post = await res.json();
return (
<article>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
);
}
在这个例子中,app/layout.tsx
定义了整个应用的根布局,包括头部和底部。app/blog/layout.tsx
定义了博客部分的布局,包括一个标题。app/blog/[id]/page.tsx
是具体的博客文章页面,它会自动嵌套在 app/blog/layout.tsx
和 app/layout.tsx
中。
嵌套 layout
的工作原理
当 Next.js 渲染页面时,它会从根目录开始,逐级查找 layout
文件,并将它们嵌套在一起。每个 layout
都会通过 children
属性接收子组件的内容,最终形成一个完整的页面结构。
例如,当你访问 /blog/1
时,Next.js 会按照以下顺序渲染组件:
app/layout.tsx
(根布局)app/blog/layout.tsx
(博客布局)app/blog/[id]/page.tsx
(具体文章页面)
最终的页面结构如下:
<html lang="en">
<body>
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section>
<h1>Blog</h1>
<article>
<h2>Post Title</h2>
<p>Post Body</p>
</article>
</section>
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
总结
通过动态路由,你可以根据 URL 参数动态加载页面内容,这非常适合构建需要根据参数显示不同内容的页面。而 layout
组件则允许你定义页面的共享结构,通过嵌套 layout
,你可以轻松地构建复杂的页面布局。
6. 客户端与服务器端组件
Next.js 默认使用服务器端组件模式(Server Components),即先在服务器端渲染 HTML,然后返回给客户端。
如果你想使用客户端模式,只需要在 page.tsx
的开头写上 'use client'
即可:
'use client'
服务器端组件性能更好,因为它们返回的是 HTML 而不是 JavaScript。但是,一些复杂的动画和交互需要客户端模式来实现。
7. Hooks
Hooks 只能用于客户端模式。常用的有 useState
和 useEffect
。
'use client'
import { useState, useEffect } from "react"
export default function NewBlog2() {
const [count, setCount] = useState(0);
const [input, setInput] = useState('');
useEffect(() => {
setCount(100);
}, []);
return (
<div>
<h1>New Blog 2 Page</h1>
<b>这里是 useState 的相关演示</b>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<button onClick={() => setCount(count - 1)}>sub</button>
<br />
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<p>Input: {input}</p>
<br />
<br />
<b>这里是 useEffect 的相关演示</b>
</div>
);
}
这里,count
是一个变量,setCount
是一个函数,用于操作 count
。逻辑是 onClick={() => setCount(操作)}
。
useEffect
用于异步操作或者获取第三方 API 数据。最后的 []
里面可以放变量,后续只要这个变量修改了就会触发一次 useEffect
。
8. 数据获取 (Data Fetching)
你可以使用 useEffect
来获取数据:
useEffect(() => {
const fetchPost = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await res.json();
setPost(data);
};
fetchPost();
}, []);
或者,你可以直接将整个组件做成异步组件,这样就不需要在开头声明模式了,更加方便快捷:
export default async function BlogPost() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await res.json();
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
9. 后端 API
Next.js 是一个强大的全栈框架,支持后端 API 的开发。你可以参考 Next.js 官方文档中的 Route Handlers。
后端路由与前端路由类似,文件路径决定了 API 的地址。后端文件通常是 .ts
文件,而不是 .tsx
文件。
使用 Axios 实例处理前后端交互
Axios 是一个基于 Promise 的 HTTP 客户端,适用于浏览器和 Node.js,非常适合用于处理前后端交互。你可以在 Next.js 的后端 API 路由中使用 Axios 来发送 HTTP 请求。
首先,确保你已经安装了 Axios:
pnpm install axios
然后,配置一个 Axios 实例,如下所示:
// utils/axios.ts
import axios from 'axios';
const request = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com', // 设置请求的基础路径
timeout: 3000000, // 设置请求超时时间(这个时间因个人而定,up本人喜欢时间长一点呢)
});
// 添加请求拦截器
request.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
request.interceptors.response.use(
(response) => {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data; // 直接返回响应数据
},
(error) => {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
console.error('Request failed:', error);
return Promise.reject(error);
}
);
export default request;
接下来,你可以使用这个配置好的 Axios 实例来处理 API 请求。
创建一个简单的 API
// app/api/blog/route.ts
import request from '@/utils/axios';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
try {
const data = await request.get('/posts/1'); // 使用配置好的 Axios 实例
return NextResponse.json(data);
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 });
}
}
这个 API 的路径将是 http://localhost:3000/api/blog
。
其中,return NextResponse.json(data)
用于将数据以 JSON 格式返回给客户端。它会自动设置响应的 Content-Type
为 application/json
,并默认将状态码设置为 200(请求成功)。如果需要,你还可以通过传递一个选项对象来设置其他状态码,例如 return NextResponse.json({ error: 'Not found' }, { status: 404 })
用于表示资源未找到。
添加更多的 HTTP 方法
你可以根据需要添加更多的 HTTP 方法,如 POST
、PUT
、DELETE
等:
// app/api/blog/route.ts
import request from '@/utils/axios';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
try {
const data = await request.get('/posts/1'); // 使用配置好的 Axios 实例
return NextResponse.json(data);
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const data = await request.json();
const response = await request.post('/posts', data); // 使用配置好的 Axios 实例
return NextResponse.json({ message: 'Data received', data: response });
} catch (error) {
console.error('Error posting data:', error);
return NextResponse.json({ error: 'Failed to post data' }, { status: 500 });
}
}
使用 Axios 实例的优势
- 简洁易用:Axios 提供了简洁的 API,易于上手和使用。
- 功能强大:支持请求和响应拦截器,可以方便地处理请求和响应数据。
- 跨浏览器兼容:在各种浏览器环境中都能稳定运行。
- 支持 Promise:基于 Promise 的设计,使得异步请求更加方便。
- 统一配置:通过配置 Axios 实例,可以统一管理请求和响应的处理逻辑,减少重复代码。
示例:使用 Axios 实例创建一个动态路由 API
假设你有一个博客应用,每个博客文章都有一个唯一的 ID。你可以使用 Axios 实例来创建一个动态路由 API,根据文章 ID 获取文章内容:
// app/api/blog/[id]/route.ts
import request from '@/utils/axios';
import { NextResponse } from 'next/server';
export async function GET(request: Request, { params }: { params: { id: string } }) {
try {
const { data } = await request.get(`/posts/${params.id}`); // 从外部 API 解构获取数据
return NextResponse.json(data); // 将数据以 JSON 格式返回给前端
} catch (error) {
console.error('Error fetching post:', error);
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
}
这个 API 的路径将是 http://localhost:3000/api/blog/1
,其中 1
是动态参数 id
的值。
前端调用 API 并渲染数据
接下来,我们将在 Next.js 的前端页面中调用这个 API 并渲染数据。假设你有一个页面组件 BlogPostPage
,它根据文章 ID 获取文章内容并渲染。
创建前端页面组件
// app/blog/[id]/page.tsx
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import request from '@/utils/axios';
export default function BlogPostPage() {
const { id } = useParams(); // 获取动态参数 id
const [post, setPost] = useState(null);
useEffect(() => {
async function fetchPost() {
try {
const response = await request.get(`http://localhost:3000/api/blog/${id}`); // 调用后端 API
setPost(response);
} catch (error) {
console.error('Error fetching post:', error);
}
}
if (id) {
fetchPost();
}
}, [id]);
if (!post) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
说明
获取动态参数:
- 使用
useParams
钩子从 Next.js 的路由中获取动态参数id
。
- 使用
调用后端 API:
- 使用之前配置好的 Axios 实例
request
来调用后端 API。 - 在
useEffect
中调用 API,并将返回的数据存储在状态post
中。
- 使用之前配置好的 Axios 实例
渲染数据:
- 如果
post
数据尚未加载完成,显示一个加载中的提示。 - 一旦数据加载完成,渲染文章的标题和内容。
- 如果
完整的前端页面代码
// app/blog/[id]/page.tsx
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import request from '@/utils/axios';
export default function BlogPostPage() {
const { id } = useParams(); // 获取动态参数 id
const [post, setPost] = useState(null);
useEffect(() => {
async function fetchPost() {
try {
const response = await request.get(`http://localhost:3000/api/blog/${id}`); // 调用后端 API
setPost(response);
} catch (error) {
console.error('Error fetching post:', error);
}
}
if (id) {
fetchPost();
}
}, [id]);
if (!post) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
10. 总结
通过以上内容,你应该能够快速上手 Next.js,并开始构建自己的 Web 应用。Next.js 提供了许多强大的功能,如服务器端渲染、静态生成和后端 API 开发,能够帮助你构建高性能、可扩展的 Web 应用。
希望这篇指南对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言。
以上是完整的 Next.js 快速上手指南,涵盖了从项目搭建到后端 API 开发的各个方面。如果你还有其他需要补充或修改的地方,请随时告诉我。