Next.js 15 全栈开发实战指南

Next.js 从入门到深入(2025 最新版)

前言

本文基于 Next.js 15(2025 稳定版,React 19 兼容),聚焦「App Router 生态」(Next.js 13+ 主流方案),从「环境搭建→基础语法→框架设计→底层原理→实战优化」逐步递进,既适合自学,也适合向入门者讲解(每个知识点包含「是什么→为什么→怎么用」,配合可运行示例和通俗解释)。

核心目标:

  1. 让入门者掌握 Next.js 全栈开发能力(前端 UI + 后端 API + 数据库);
  2. 理解 Next.js 混合渲染、文件路由等核心设计;
  3. 覆盖最新特性(Turbopack 稳定版、React Server Components 增强、Server Actions 正式版、React 19 集成)。

第一部分:入门基础(写给新手,1 周上手)

1.1 什么是 Next.js?

核心定义

Next.js 是 Vercel 推出的 React 全栈框架,基于 React 构建,提供「路由系统、混合渲染、API 路由、零配置优化」等能力,让 React 项目从「单页应用(SPA)」升级为「全栈应用(Full-Stack)」。

和传统 React SPA 的区别
特性 传统 React SPA(如 Create React App) Next.js 全栈应用
路由系统 需手动配置 react-router 文件系统路由(文件夹即路由,零配置)
渲染方式 仅客户端渲染(CSR),首屏慢 混合渲染(SSG/SSR/ISR/CSR 自由切换)
后端能力 无,需单独搭建服务器 内置 API 路由,可直接写后端逻辑
构建优化 需手动配置 Webpack/Vite 零配置(Tree Shaking、代码分割、图片优化)
SEO 支持 弱(依赖客户端渲染,爬虫难抓取) 强(服务端渲染/静态生成,HTML 完整)
核心优势
  • 「零配置开箱即用」:无需配置 Webpack、Babel,自带路由、优化功能;
  • 「混合渲染无敌」:根据场景选择渲染方式(静态页面用 SSG,动态页面用 SSR);
  • 「全栈开发体验」:前端 UI + 后端 API + 数据库(适配 Prisma/MongoDB 等)一站式开发;
  • 「性能自动优化」:图片、字体、脚本自动优化,首屏加载速度远超传统 SPA;
  • 「React 生态无缝衔接」:会 React 就能快速上手,复用 React 组件和 Hooks。

1.2 环境搭建(最新稳定版)

Next.js 环境搭建极其简单,仅需 Node.js 18+(Next.js 15 最低要求)。

前置依赖
步骤 1:创建 Next.js 15 项目

使用官方脚手架 create-next-app,支持一键生成项目(默认启用 App Router、Tailwind CSS、ESLint):

bash 复制代码
# 创建项目(指定 Next.js 15 版本)
npx create-next-app@latest my-next-app --version 15.0.0

# 交互式配置(按需求选择,新手推荐默认)
✔ Would you like to use TypeScript? ... Yes(推荐 TypeScript,类型安全)
✔ Would you like to use ESLint? ... Yes(代码检查)
✔ Would you like to use Tailwind CSS? ... Yes(样式框架,默认启用)
✔ Would you like to use `src/` directory? ... Yes(源码目录分离)
✔ Would you like to use App Router? (recommended) ... Yes(核心路由方案)
✔ Would you like to customize the default import alias? ... No(默认导入别名)
步骤 2:启动项目
bash 复制代码
# 进入项目目录
cd my-next-app

# 启动开发服务器(默认使用 Turbopack,比 Webpack 快 700%)
npm run dev
步骤 3:访问项目

打开浏览器访问 http://localhost:3000,即可看到 Next.js 默认首页:

  • 开发模式支持「热重载」:修改代码后页面实时更新,无需手动刷新;
  • 控制台输出路由、优化等信息,便于调试。
常见问题
  • 端口占用:修改 package.json 中的 dev 脚本为 next dev -p 3001(指定端口 3001);
  • Turbopack 兼容问题:若部分依赖不支持 Turbopack,可切换回 Webpack:npm run dev -- --turbo=false
  • 网络访问:想让局域网内其他设备访问,添加 --hostname 0.0.0.0next dev --hostname 0.0.0.0

1.3 项目结构深度解析(App Router 核心)

Next.js 15 默认使用 App Router(替代旧版 Pages Router),项目结构如下(仅保留核心文件/文件夹):

复制代码
my-next-app/
├── src/
│   ├── app/                # 核心路由目录(文件夹即路由)
│   │   ├── page.tsx        # 首页(路由:/)
│   │   ├── layout.tsx      # 根布局(所有页面共享)
│   │   ├── error.tsx       # 全局错误边界
│   │   ├── loading.tsx     # 全局加载状态
│   │   └── api/            # API 路由目录(后端接口)
│   │       └── hello/
│   │           └── route.ts # 接口:/api/hello
│   ├── components/         # 公共组件目录
│   └── lib/                # 工具函数、数据库连接等
├── public/                 # 静态资源目录(图片、字体等)
├── tailwind.config.js      # Tailwind CSS 配置
├── next.config.js          # Next.js 核心配置
└── package.json            # 依赖配置
核心文件/文件夹说明(新手必记)
  1. src/app/:路由核心目录,遵循「文件系统路由规则」:

    • page.tsx:路由入口文件(必须有,否则路由不生效),对应路径为文件夹名称;
    • layout.tsx:布局组件(共享 UI,如导航栏、页脚),嵌套路由自动继承父布局;
    • loading.tsx:加载状态组件(路由切换时自动显示,支持 Suspense);
    • error.tsx:错误边界组件(当前路由出错时显示,不影响全局);
    • api/:API 路由目录,route.ts 为接口处理文件(支持 GET/POST 等方法)。
  2. public/:静态资源目录,文件可通过根路径直接访问:

    • 示例:public/logo.svg 可通过 http://localhost:3000/logo.svg 访问;
    • 组件中引用:import Image from 'next/image'; <Image src="/logo.svg" alt="Logo" width={100} height={100} />
  3. src/components/ :公共组件目录(如 Button、Card 等),建议按功能分类(如 ui/layout/)。

  4. next.config.js:Next.js 配置文件(如启用 HTTPS、配置代理、扩展 Webpack 等)。

1.4 第一个 Next.js 程序:Hello World + 路由跳转

基于默认项目改造,实现「首页显示 Hello World + 跳转到详情页」,理解 App Router 核心用法。

步骤 1:修改首页(src/app/page.tsx
tsx 复制代码
// 首页(路由:/)
import Link from 'next/link'; // Next.js 内置路由组件(替代 a 标签,优化跳转)
import { Button } from '@/components/ui/Button'; // 假设已创建公共组件

export default function Home() {
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold text-center text-gray-800 mb-8">
        Hello Next.js 15!
      </h1>
      <div className="flex justify-center gap-4">
        {/* 方式 1:Link 组件跳转(客户端导航,无刷新) */}
        <Link 
          href="/about" 
          className="px-6 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
        >
          跳转到关于页
        </Link>

        {/* 方式 2:按钮跳转(通过 onClick + 编程式导航) */}
        <Button 
          onClick={() => window.location.href = '/about'} 
          variant="secondary"
        >
          编程式跳转
        </Button>
      </div>
    </div>
  );
}
步骤 2:创建关于页(src/app/about/page.tsx

按「文件系统路由」规则,创建 about 文件夹 + page.tsx,自动生成路由 /about

tsx 复制代码
// 关于页(路由:/about)
import Link from 'next/link';
import Image from 'next/image'; // Next.js 优化图片组件(自动压缩、懒加载)

export default function About() {
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold text-center text-gray-800 mb-8">关于我们</h1>
      
      {/* Next.js Image 组件(优化图片性能) */}
      <div className="flex justify-center mb-8">
        <Image
          src="/nextjs-logo.png" // 静态资源放在 public/ 目录
          alt="Next.js Logo"
          width={200} // 必须指定宽高(或用 fill 自适应)
          height={200}
          className="rounded-lg"
        />
      </div>

      <p className="text-center text-gray-600 mb-8">
        基于 Next.js 15 构建的全栈应用,支持混合渲染、API 路由、自动优化。
      </p>

      {/* 返回首页 */}
      <div className="flex justify-center">
        <Link 
          href="/" 
          className="px-6 py-3 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 transition-colors"
        >
          返回首页
        </Link>
      </div>
    </div>
  );
}
步骤 3:创建共享布局(src/app/layout.tsx

布局组件用于共享 UI(如导航栏、页脚),所有子路由自动继承:

tsx 复制代码
// 根布局(所有页面共享)
import type { Metadata } from 'next'; // 元数据类型
import { Inter } from 'next/font/google'; // Next.js 字体优化
import './globals.css'; // 全局样式(Tailwind 入口)
import Header from '@/components/layout/Header'; // 导航栏组件
import Footer from '@/components/layout/Footer'; // 页脚组件

// 导入 Google 字体(自动优化:预加载、子集化、无布局偏移)
const inter = Inter({ subsets: ['latin'] });

// 页面元数据(SEO 优化,自动生成 title、description)
export const metadata: Metadata = {
  title: 'Next.js 15 入门教程',
  description: '从入门到深入掌握 Next.js 全栈开发',
};

// 根布局必须接收 children(子页面内容)
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body className={`${inter.className} min-h-screen flex flex-col`}>
        <Header /> {/* 共享导航栏 */}
        <main className="flex-1">
          {children} {/* 子页面内容(首页、关于页等) */}
        </main>
        <Footer /> {/* 共享页脚 */}
      </body>
    </html>
  );
}
核心知识点(新手必记)
  1. 路由规则

    • 文件夹名称 = 路由路径(如 about 文件夹 → /about 路由);
    • page.tsx 是路由唯一入口(缺少则路由不生效);
    • 嵌套文件夹 = 嵌套路由(如 app/blog/[id]/page.tsx/blog/123 动态路由)。
  2. Link 组件 vs a 标签

    • Link 是 Next.js 内置组件,实现「客户端导航」(无页面刷新,性能更优);
    • 避免使用 a 标签(会触发全页刷新,破坏 SPA 体验);
    • 跳转外部链接时仍用 a 标签(如 <a href="https://nextjs.org">Next.js 官网</a>)。
  3. Image 组件优势

    • 自动压缩图片(WebP/AVIF 格式);
    • 懒加载(默认开启,滚动到可视区域才加载);
    • 防止布局偏移(CLS):必须指定 width/heightfill 属性;
    • 支持远程图片(需在 next.config.js 配置 images.domains 白名单)。

1.5 核心语法:路由、组件、数据获取

1.5.1 路由进阶(动态路由 + 嵌套路由)

Next.js 路由支持「动态路由」(如 /blog/123)和「嵌套路由」(如 /dashboard/profile),是构建复杂应用的基础。

动态路由([param] 文件夹)

用于处理动态参数(如文章 ID、用户 ID),文件夹名称用 [参数名] 表示:

  1. 创建动态路由目录:src/app/blog/[id]/
  2. 创建入口文件:src/app/blog/[id]/page.tsx
tsx 复制代码
// 动态路由:/blog/[id](id 为动态参数)
import { notFound } from 'next/navigation'; // 404 页面跳转
import Link from 'next/link';

// 接收动态参数(params 包含路由中的动态值)
export default function BlogPost({ params }: { params: { id: string } }) {
  // 模拟文章数据
  const posts = [
    { id: '1', title: 'Next.js 15 新特性', content: 'Turbopack 稳定版、Server Actions 正式支持...' },
    { id: '2', title: 'App Router 完全指南', content: '文件路由、嵌套布局、加载状态...' },
  ];

  // 查找当前文章
  const post = posts.find(p => p.id === params.id);

  // 文章不存在时跳转 404
  if (!post) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <Link href="/blog" className="text-blue-600 hover:underline mb-4 inline-block">
        ← 返回文章列表
      </Link>
      <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
      <p className="text-gray-700">{post.content}</p>
    </div>
  );
}
  1. 创建文章列表页(src/app/blog/page.tsx):
tsx 复制代码
// 文章列表页:/blog
import Link from 'next/link';

export default function BlogList() {
  const posts = [
    { id: '1', title: 'Next.js 15 新特性' },
    { id: '2', title: 'App Router 完全指南' },
  ];

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8 text-center">文章列表</h1>
      <div className="grid gap-4 max-w-2xl mx-auto">
        {posts.map(post => (
          <Link 
            key={post.id}
            href={`/blog/${post.id}`} // 跳转动态路由
            className="p-4 border rounded-md hover:border-blue-500 transition-colors"
          >
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="text-gray-500 mt-1">点击查看详情</p>
          </Link>
        ))}
      </div>
    </div>
  );
}
嵌套路由(共享子布局)

嵌套路由用于实现「子页面共享父布局」(如 /dashboard 下的 profilesettings 共享仪表盘导航):

  1. 创建父布局:src/app/dashboard/layout.tsx(子路由共享的布局);
  2. 创建子页面:src/app/dashboard/profile/page.tsxsrc/app/dashboard/settings/page.tsx

父布局代码(src/app/dashboard/layout.tsx):

tsx 复制代码
// 仪表盘父布局(/dashboard 下所有子路由共享)
import Link from 'next/link';

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex">
      {/* 侧边导航(共享) */}
      <aside className="w-64 border-r p-4 h-[calc(100vh-120px)]">
        <h2 className="text-xl font-bold mb-4">仪表盘</h2>
        <nav className="flex flex-col gap-2">
          <Link 
            href="/dashboard/profile" 
            className="p-2 rounded hover:bg-gray-100"
          >
            个人资料
          </Link>
          <Link 
            href="/dashboard/settings" 
            className="p-2 rounded hover:bg-gray-100"
          >
            账号设置
          </Link>
        </nav>
      </aside>
      {/* 子页面内容 */}
      <main className="flex-1 p-6">
        {children}
      </main>
    </div>
  );
}

子页面代码(src/app/dashboard/profile/page.tsx):

tsx 复制代码
// 个人资料页:/dashboard/profile
export default function Profile() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-4">个人资料</h1>
      <p className="text-gray-700">用户名:nextjs-dev</p>
      <p className="text-gray-700 mt-2">邮箱:dev@nextjs.org</p>
    </div>
  );
}
1.5.2 组件类型:Server Components vs Client Components

Next.js 15 核心特性之一是「React Server Components(RSC)」,组件默认是 Server Components(服务器端组件),无需发送 JS 到客户端,仅返回 HTML,大幅减少 bundle 体积。

核心区别(新手必懂)
特性 Server Components(默认) Client Components(需标记)
运行环境 服务器端(Node.js) 客户端浏览器
JS 发送到客户端 否(仅返回 HTML) 是(发送组件 JS 代码)
支持的 API Node.js API(如 fs、axios) 浏览器 API(如 window、document)
支持的 React 特性 无 Hooks 限制(除客户端相关 Hooks) 支持所有 Hooks(useState、useEffect 等)
标记方式 无需标记(默认) 顶部添加 'use client' 指令
适用场景 静态内容、数据获取、无交互组件 交互组件(按钮、表单)、使用浏览器 API
实战示例
  1. Server Component(默认,无需标记):src/components/ui/PostCard.tsx
tsx 复制代码
// Server Component(无 'use client',运行在服务器端)
import Image from 'next/image';

// 接收文章数据(服务器端渲染,无需发送 JS 到客户端)
export default function PostCard({ post }: { post: { id: string; title: string; cover: string } }) {
  return (
    <div className="border rounded-md p-4">
      <div className="h-40 relative mb-4">
        <Image
          src={post.cover}
          alt={post.title}
          fill // 自适应父容器
          className="object-cover rounded"
        />
      </div>
      <h3 className="text-xl font-semibold">{post.title}</h3>
      <p className="text-gray-500 mt-2">文章 ID:{post.id}</p>
    </div>
  );
}
  1. Client Component(需标记 'use client'):src/components/ui/Button.tsx
tsx 复制代码
// Client Component(必须顶部添加 'use client',运行在客户端)
'use client';

import { useState } from 'react';

type ButtonVariant = 'primary' | 'secondary';

interface ButtonProps {
  children: React.ReactNode;
  variant?: ButtonVariant;
  onClick?: () => void;
}

export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
  // 客户端状态(仅 Client Component 支持 useState)
  const [isHovered, setIsHovered] = useState(false);

  // 样式根据变体和状态动态切换
  const baseStyles = 'px-4 py-2 rounded-md transition-colors';
  const variantStyles = variant === 'primary' 
    ? `bg-blue-600 text-white ${isHovered ? 'bg-blue-700' : ''}`
    : `bg-gray-200 text-gray-800 ${isHovered ? 'bg-gray-300' : ''}`;

  return (
    <button
      className={`${baseStyles} ${variantStyles}`}
      onClick={onClick}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      {children}
    </button>
  );
}
关键规则
  • 「自上而下」:父组件是 Server Component,子组件默认也是 Server Component;
  • 「标记强制」:使用 useStateuseEffect、浏览器 API 等客户端特性时,必须添加 'use client'(否则报错);
  • 「数据获取」:Server Component 可直接用 async/await 获取数据(客户端组件需用 SWR/React Query)。
1.5.3 数据获取(Server Components 异步获取 + 客户端 SWR)

Next.js 数据获取分为「服务器端获取」(Server Components)和「客户端获取」(Client Components),按需选择。

1. Server Components 异步获取(推荐,无需客户端 JS)

Server Components 支持直接用 async/await 获取数据(运行在服务器端,无跨域问题):

tsx 复制代码
// 文章列表页(Server Component,异步获取数据)
import PostCard from '@/components/ui/PostCard';

// 异步组件(Server Component 支持 async/await)
export default async function BlogList() {
  // 服务器端获取数据(可调用 Node.js API、数据库、第三方接口)
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=6', {
    next: { revalidate: 60 }, // 增量静态再生(ISR):60 秒重新验证数据
  });

  // 处理错误(数据获取失败时)
  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }

  const posts = await res.json();

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8 text-center">最新文章</h1>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {posts.map((post: { id: number; title: string }) => (
          <PostCard
            key={post.id}
            post={{
              id: post.id.toString(),
              title: post.title,
              cover: `https://picsum.photos/seed/${post.id}/400/300`, // 随机图片
            }}
          />
        ))}
      </div>
    </div>
  );
}
2. Client Components 数据获取(SWR 库)

客户端组件需用数据获取库(如 SWR、React Query)实现缓存、重新验证等功能,Next.js 官方推荐 SWR:

  1. 安装 SWR:

    bash 复制代码
    npm install swr
  2. 客户端组件数据获取示例:src/components/ClientPostList.tsx

tsx 复制代码
// Client Component(需标记 'use client')
'use client';

import useSWR from 'swr';
import PostCard from './PostCard';

// 数据获取函数
const fetcher = async (url: string) => {
  const res = await fetch(url);
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
};

export default function ClientPostList() {
  // SWR 钩子:缓存数据、自动重新验证、错误处理
  const { data, error, isLoading } = useSWR(
    'https://jsonplaceholder.typicode.com/posts?_limit=3',
    fetcher,
    {
      revalidateOnFocus: true, // 页面聚焦时重新验证数据
      dedupingInterval: 30000, // 30 秒内避免重复请求
    }
  );

  // 加载状态
  if (isLoading) {
    return <div className="text-center py-8">加载中...</div>;
  }

  // 错误状态
  if (error) {
    return <div className="text-center py-8 text-red-600">加载失败,请重试</div>;
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-2xl font-bold mb-6 text-center">客户端渲染文章列表</h1>
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        {data?.map((post: { id: number; title: string }) => (
          <PostCard
            key={post.id}
            post={{
              id: post.id.toString(),
              title: post.title,
              cover: `https://picsum.photos/seed/${post.id}/400/300`,
            }}
          />
        ))}
      </div>
    </div>
  );
}
数据获取策略总结
场景 推荐方案 核心优势
静态数据(如博客文章) Server Components + ISR(revalidate) 首屏快、缓存优化、无需客户端 JS
动态数据(如用户信息) Server Components + SSR(无 revalidate) 实时数据、服务器端渲染、SEO 友好
客户端交互数据(如搜索) Client Components + SWR 缓存、聚焦重新验证、无页面刷新
1.5.4 样式方案(Tailwind CSS 为主)

Next.js 15 默认集成 Tailwind CSS(最流行的原子化 CSS 框架),同时支持 CSS Modules、Sass 等方案。

Tailwind CSS 基础使用

直接在组件中使用 Tailwind 类名(无需手动写 CSS):

tsx 复制代码
<div className="flex justify-center items-center h-64 bg-gray-100 rounded-lg shadow-sm">
  <h2 className="text-2xl font-bold text-blue-600">Tailwind CSS 示例</h2>
</div>
自定义 Tailwind 配置(tailwind.config.js

扩展颜色、字体、间距等:

js 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: {
        primary: '#165DFF', // 自定义主色
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
    },
  },
  plugins: [],
};
CSS Modules(组件样式隔离)

创建 xxx.module.css 文件,类名自动哈希,避免样式冲突:

  1. 创建样式文件:src/components/ui/Card.module.css
css 复制代码
/* Card.module.css */
.card {
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
  padding: 1.5rem;
  transition: box-shadow 0.2s;
}

.card:hover {
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.title {
  font-size: 1.25rem;
  font-weight: 600;
  margin-bottom: 0.5rem;
}
  1. 组件中使用:
tsx 复制代码
import styles from './Card.module.css';

export default function Card({ title, children }) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      <div>{children}</div>
    </div>
  );
}

1.6 API 路由(全栈开发核心)

Next.js 内置 API 路由,可直接在 app/api/ 目录下创建后端接口,无需单独搭建服务器。

基础 API 接口(GET 请求)

创建 src/app/api/hello/route.ts(API 路由文件必须命名为 route.ts):

tsx 复制代码
// API 接口:GET /api/hello
import type { NextRequest } from 'next/server';

// GET 请求处理函数
export async function GET(request: NextRequest) {
  // 获取查询参数(如 /api/hello?name=nextjs)
  const name = request.nextUrl.searchParams.get('name') || 'Guest';

  // 返回 JSON 响应
  return Response.json({
    message: `Hello ${name}!`,
    time: new Date().toISOString(),
  });
}

访问 http://localhost:3000/api/hello?name=dev,返回结果:

json 复制代码
{
  "message": "Hello dev!",
  "time": "2025-01-01T12:00:00.000Z"
}
带参数的 API 接口(POST 请求)

创建 src/app/api/posts/[id]/route.ts(动态 API 路由):

tsx 复制代码
// API 接口:GET /api/posts/123、POST /api/posts/123
import type { NextRequest } from 'next/server';

// GET 请求:获取单篇文章
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } } // 动态参数
) {
  const postId = params.id;

  // 模拟从数据库获取数据
  const post = {
    id: postId,
    title: `文章 ${postId}`,
    content: '这是一篇测试文章',
  };

  return Response.json(post);
}

// POST 请求:更新文章
export async function POST(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // 解析请求体
  const body = await request.json();
  const { title, content } = body;

  // 模拟更新数据库
  const updatedPost = {
    id: params.id,
    title: title || `文章 ${params.id}(更新后)`,
    content: content || '更新后的内容',
  };

  return Response.json({
    message: '文章更新成功',
    post: updatedPost,
  });
}

测试 POST 请求(用 Postman 或 curl):

bash 复制代码
curl -X POST http://localhost:3000/api/posts/123 \
  -H "Content-Type: application/json" \
  -d '{"title":"Next.js API 实战","content":"API 路由太方便了!"}'

返回结果:

json 复制代码
{
  "message": "文章更新成功",
  "post": {
    "id": "123",
    "title": "Next.js API 实战",
    "content": "API 路由太方便了!"
  }
}
API 路由核心知识点
  1. 文件名必须为 route.ts(或 route.js),否则 Next.js 不识别;
  2. 支持的请求方法:GETPOSTPUTDELETE 等,对应导出同名函数;
  3. 动态 API 路由:用 [param] 文件夹接收动态参数(如 [id]);
  4. 请求处理:
    • 获取查询参数:request.nextUrl.searchParams.get('key')
    • 解析请求体:await request.json()(POST/PUT 请求);
    • 返回响应:Response.json(data, { status: 200 })
  5. 无跨域问题:API 接口和前端页面同域名,客户端请求无需处理 CORS。

第二部分:框架设计(进阶理解,知其然知其所以然)

2.1 Next.js 核心设计思想

2.1.1 全栈框架:前端 + 后端一体化

Next.js 的核心设计是「打破前端和后端的边界」,让开发者用一套代码库完成全栈开发,核心思路:

  1. 文件系统统一路由 :前端页面路由(app/xxx/page.tsx)和后端 API 路由(app/api/xxx/route.ts)共用一套文件系统规则,降低学习成本;
  2. 共享代码和类型:前端组件和后端 API 可共享 TypeScript 类型定义、工具函数,避免重复代码;
  3. 服务器端能力下沉:前端开发者无需学习 Node.js/Express,直接通过 API 路由、Server Components 调用服务器资源(数据库、文件系统、第三方接口)。
2.1.2 混合渲染:按需选择最优渲染策略

Next.js 不强制单一渲染方式,而是提供「混合渲染」能力,让每个页面/组件都能选择最适合的渲染策略,核心渲染方式:

渲染方式 英文缩写 核心原理 适用场景 优势
静态站点生成 SSG 构建时(build time)生成静态 HTML,部署到 CDN 静态页面(博客、文档、营销页) 首屏最快、SEO 最优、CDN 缓存
服务器端渲染 SSR 每次请求(request time)在服务器生成 HTML,返回给客户端 动态页面(实时数据、用户专属内容) 数据实时性高、SEO 友好
增量静态再生 ISR 构建时生成静态 HTML,后续通过 revalidate 机制增量更新 半动态页面(商品列表、新闻列表) 兼顾静态速度和数据新鲜度
客户端渲染 CSR 服务器返回空 HTML + JS,客户端加载 JS 后渲染 交互密集页面(仪表盘、编辑器) 无服务器依赖、交互流畅

Next.js 渲染策略选择逻辑:

  • 默认:Server Components 优先使用 SSG(构建时生成 HTML);
  • 动态数据:添加 revalidate 选项启用 ISR,或不设置 revalidate 启用 SSR;
  • 客户端交互:Client Components 自动使用 CSR。
2.1.3 零配置优化:开发者聚焦业务

Next.js 内置大量优化功能,无需手动配置,核心优化点:

  1. 代码分割:按路由分割 JS 代码,仅加载当前页面所需代码;
  2. 图片优化next/image 组件自动压缩、懒加载、格式转换;
  3. 字体优化next/font 自动预加载字体、避免布局偏移;
  4. 脚本优化next/script 组件支持异步加载、延迟加载第三方脚本;
  5. 缓存策略:自动缓存静态资源、API 响应、页面 HTML;
  6. Turbopack:替代 Webpack 的构建工具,增量构建速度提升 700%。

2.2 App Router 设计原理

App Router 是 Next.js 13+ 推出的新一代路由系统,替代旧版 Pages Router,核心设计围绕「嵌套布局、React Server Components、加载状态、错误边界」展开。

2.2.1 路由匹配规则

App Router 遵循「基于文件系统的路由匹配」,核心规则:

  1. 路由入口page.tsx 是路由的唯一入口,缺少则路由不生效;
  2. 布局继承 :每个文件夹下的 layout.tsx 为当前路由及子路由提供共享布局;
  3. 嵌套路由:文件夹嵌套对应路由嵌套,子路由自动继承父布局;
  4. 动态路由[param] 文件夹匹配动态参数(如 [id] 匹配 /123[slug] 匹配 /hello-world);
  5. Catch-All 路由[...param] 匹配多个动态参数(如 [...slug] 匹配 /blog/2025/01);
  6. 可选 Catch-All 路由[[...param]] 匹配可选参数(如 [[...slug]] 既匹配 /,也匹配 /blog/123)。
2.2.2 布局嵌套与数据流

App Router 的布局是「嵌套式」的,数据流从父布局到子布局再到页面,示例结构:

复制代码
app/
├── layout.tsx(根布局)
├── page.tsx(/ 路由)
├── blog/
│   ├── layout.tsx(blog 布局,继承根布局)
│   ├── page.tsx(/blog 路由,继承 blog 布局)
│   └── [id]/
│       ├── layout.tsx([id] 布局,继承 blog 布局)
│       └── page.tsx(/blog/123 路由,继承 [id] 布局)

数据流流程:

  1. 根布局(app/layout.tsx)接收 children(子布局/页面);
  2. blog 布局(app/blog/layout.tsx)接收 childrenblog/page.tsx[id] 布局);
  3. 数据可通过布局 props 向下传递,或通过 React Context 共享。
2.2.3 加载状态与错误边界

App Router 内置「加载状态」和「错误边界」,无需手动编写复杂逻辑:

  1. 加载状态(loading.tsx

    • 路由切换时自动显示,直到目标页面/布局加载完成;
    • 支持 Suspense 特性,可实现局部加载状态(而非全局);
    • 示例:app/blog/loading.tsx(仅 blog 路由切换时显示)。
  2. 错误边界(error.tsx

    • 捕获当前路由及子路由的错误(数据获取失败、组件渲染错误等);
    • 错误发生时显示错误页面,不影响全局应用;
    • 支持错误恢复(如添加「重试」按钮重新加载数据)。

2.3 React Server Components 集成设计

Next.js 是 React Server Components(RSC)的核心落地框架,其设计深度集成 RSC,核心目标:

  1. 减少客户端 JS 体积:Server Components 运行在服务器端,不发送 JS 到客户端,仅返回 HTML 和组件数据;
  2. 提升首屏加载速度:HTML 直接从服务器返回,客户端无需等待 JS 加载完成即可渲染;
  3. 服务器端能力访问:Server Components 可直接调用 Node.js API、数据库,无需通过 API 接口转发;
  4. 无缝混合使用:Server Components 和 Client Components 可自由嵌套(父 Server → 子 Client,或父 Client → 子 Server)。
RSC 工作流程(Next.js 实现)
  1. 构建/请求时,Next.js 识别组件类型(Server/Client);
  2. Server Components 在服务器端执行:
    • 解析 JSX,获取数据(async/await);
    • 生成「组件描述树」(包含 HTML 片段、数据、子组件引用);
  3. Client Components 被编译为客户端 JS 代码,发送到浏览器;
  4. 浏览器接收 Server Components 的描述树和 Client Components 的 JS;
  5. 合并渲染:将 Server Components 的 HTML 与 Client Components 的交互逻辑结合,生成最终页面。

2.4 API 路由设计原理

Next.js API 路由基于「请求处理函数映射」设计,核心原理:

  1. 路由注册 :Next.js 构建时扫描 app/api/**/route.ts 文件,将其注册为 API 端点;
  2. 请求分发 :收到 API 请求时,Next.js 根据请求路径匹配对应的 route.ts 文件;
  3. 方法匹配 :根据请求方法(GET/POST 等)调用 route.ts 中导出的同名函数;
  4. 响应处理 :函数返回 Response 对象,Next.js 自动转换为 HTTP 响应。

API 路由的优势:

  • 无额外依赖:无需 Express/Koa 等后端框架,Next.js 原生支持;
  • 同构部署:API 路由和前端页面一起部署,无需单独部署服务器;
  • 类型安全:TypeScript 自动推断请求参数、响应类型,减少错误;
  • 中间件支持 :可通过 Next.js 中间件(middleware.ts)实现认证、日志、限流等功能。

2.5 生态集成设计

Next.js 设计了灵活的生态集成机制,支持与主流工具无缝衔接:

  1. 样式生态:Tailwind CSS(默认)、CSS Modules、Sass、Styled Components、Emotion 等;
  2. 数据生态:Prisma(ORM)、Mongoose(MongoDB)、Drizzle(SQL 工具)、SWR/React Query(客户端数据获取);
  3. 认证生态:NextAuth.js(官方推荐)、Clerk、Auth0 等;
  4. 部署生态:Vercel(官方,一键部署)、Netlify、AWS Amplify、Docker 等;
  5. 静态资源public/ 目录支持图片、字体、文件等静态资源,CDN 自动分发。

第三部分:底层原理(深入内核,成为专家)

3.1 渲染流程拆解(SSG/SSR/ISR)

3.1.1 静态站点生成(SSG)流程

SSG 是 Next.js 性能最优的渲染方式,流程如下:
构建阶段(npm run build) 扫描 app/ 目录下的 page.tsx 执行 Server Components(异步获取数据) 生成 HTML 文件(每个 page.tsx 对应一个 HTML) 生成静态资源(JS/CSS/图片,压缩优化) 部署到 CDN(如 Vercel、Netlify) 用户请求 CDN 直接返回静态 HTML + 资源 客户端渲染完成(无需额外请求数据)

关键细节:

  • 构建时一次性生成所有静态页面,部署后无需服务器参与渲染;
  • HTML 中包含完整内容,SEO 爬虫可直接抓取;
  • 首次加载速度极快(CDN 边缘节点分发)。
3.1.2 服务器端渲染(SSR)流程

SSR 适合动态数据场景,流程如下:
用户请求页面(如 /dashboard) 请求到达服务器(Vercel/自建服务器) Next.js 匹配对应的 page.tsx(Server Component) 执行 Server Component,异步获取实时数据(数据库/API) 生成当前请求的 HTML 内容 服务器返回 HTML + 客户端所需 JS/CSS 客户端渲染 HTML(首屏可见) 客户端加载 JS 并 hydration(激活交互)

关键细节:

  • 每次请求都在服务器生成 HTML,数据实时性高;
  • 首屏加载速度比 CSR 快(HTML 直接返回),但比 SSG 慢(需服务器处理);
  • 服务器压力较大(每次请求都要执行组件和数据获取)。
3.1.3 增量静态再生(ISR)流程

ISR 结合 SSG 和 SSR 的优势,流程如下:
构建阶段 生成静态 HTML + 资源,部署到 CDN 用户请求(首次) CDN 返回静态 HTML(SSG 模式) 到达 revalidate 时间(如 60 秒) 下一次用户请求 CDN 返回旧 HTML(同时触发服务器重新生成) 服务器重新执行组件 + 获取最新数据 生成新 HTML 并更新 CDN 缓存 后续用户请求返回新 HTML

关键细节:

  • 构建时生成初始静态页面,后续通过 revalidate 机制增量更新;
  • 重新生成期间,CDN 仍返回旧页面,不影响用户体验;
  • 兼顾静态页面的速度和动态数据的新鲜度,适合更新频率不高的页面。

3.2 React Server Components 底层原理

3.2.1 组件分类与序列化

Next.js 中组件分为三类,底层处理方式不同:

  1. Server Components(SFC)

    • 运行在服务器端,不发送 JS 到客户端;
    • 输出:「组件描述对象」(包含 HTML 片段、数据、子组件引用);
    • 序列化:通过 React 内置的序列化机制,将组件描述对象转换为 JSON,传递给客户端。
  2. Client Components(CSC)

    • 运行在客户端,需发送 JS 到客户端;
    • 输出:编译后的 JS 代码(包含组件逻辑、Hooks 等);
    • 序列化:不序列化组件本身,仅在 Server Components 的描述对象中留下「引用标记」,客户端加载后替换为实际组件。
  3. Shared Components

    • 未标记 'use client',但被 Client Components 导入的组件;
    • 自动变为 Client Components,发送 JS 到客户端。
3.2.2 数据获取底层

Server Components 数据获取的核心优势是「服务器端直接获取,无需转发」:

  • 底层支持:Node.js fetch API 增强(Next.js 封装,支持缓存、重新验证);
  • 缓存策略:
    • 默认:fetch 请求结果自动缓存(构建时获取的数据缓存到构建产物);
    • 动态缓存:添加 next: { revalidate: 60 } 启用 ISR,缓存 60 秒后重新验证;
    • 不缓存:添加 cache: 'no-store' 禁用缓存(每次请求重新获取,即 SSR)。

3.3 Turbopack 构建原理

Turbopack 是 Next.js 15 默认的构建工具(替代 Webpack),核心优化点:

  1. 增量构建:仅重新构建修改的文件,而非整个项目;
  2. 并行处理:利用多核 CPU 并行处理构建任务;
  3. 优化的依赖解析:缓存依赖关系,避免重复解析;
  4. 原生 Rust 实现:比 Webpack(JavaScript 实现)快一个数量级。

Turbopack 构建流程:

生产环境构建(npm run build):

  • Turbopack 编译所有代码,优化静态资源,生成生产级构建产物;
  • 支持代码分割、Tree Shaking、压缩等优化;
  • 输出 /.next/ 目录,包含 HTML、JS、CSS、静态资源等。

3.4 路由系统底层

Next.js 路由系统底层基于「文件系统扫描 + 路由匹配算法」:

  1. 构建时扫描

    • 构建阶段扫描 app/ 目录,识别 page.tsxroute.ts 文件;
    • 生成路由映射表(如 /app/page.tsx/api/helloapp/api/hello/route.ts)。
  2. 运行时匹配

    • 客户端导航:通过 next/linkuseRouter 触发,Next.js 从路由映射表中匹配目标页面,加载对应的组件和资源;
    • 服务端请求:服务器接收请求后,匹配路由映射表,执行对应的 page.tsx(SSR/SSG)或 route.ts(API 接口)。
  3. 动态路由匹配算法

    • 静态路由优先(如 /blog/about 优先于 /blog/[id]);
    • 动态路由参数提取(如 /blog/123 匹配 /blog/[id],提取 id=123);
    • Catch-All 路由匹配(如 /blog/2025/01 匹配 /blog/[...slug],提取 slug=['2025','01'])。

3.5 缓存机制底层

Next.js 内置多层缓存机制,优化性能和用户体验:

  1. 构建缓存

    • 构建阶段生成的静态资源(HTML、JS、CSS)缓存到 /.next/cache/
    • 二次构建时复用缓存,加速构建过程。
  2. 数据缓存

    • Server Components 中 fetch 请求默认缓存(基于请求 URL 和参数);
    • 缓存存储在 /.next/cache/fetch-cache/,生产环境部署后缓存有效。
  3. CDN 缓存

    • 静态资源(图片、字体、JS/CSS)和 SSG 页面自动设置 CDN 缓存头(Cache-Control: public, max-age=31536000);
    • ISR 页面设置 Cache-Control: s-maxage=60, stale-while-revalidate=60(缓存 60 秒,过期后后台重新验证)。
  4. 客户端缓存

    • SWR/React Query 客户端数据缓存;
    • 浏览器缓存(静态资源基于缓存头)。

第四部分:实战进阶(学以致用)

4.1 完整博客系统实战(全栈能力)

基于 Next.js 15 构建完整博客系统,包含「文章列表、详情页、发布文章、用户认证」等功能。

4.1.1 技术栈
  • 前端:Next.js 15、React 19、Tailwind CSS、TypeScript;
  • 后端:Next.js API 路由;
  • 数据库:Prisma + SQLite(开发)/ PostgreSQL(生产);
  • 认证:NextAuth.js;
  • 部署:Vercel。
4.1.2 步骤 1:初始化 Prisma ORM
  1. 安装依赖:

    bash 复制代码
    npm install prisma @prisma/client
  2. 初始化 Prisma:

    bash 复制代码
    npx prisma init
  3. 配置数据库(prisma/schema.prisma):

    prisma 复制代码
    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider = "sqlite"
      url      = env("DATABASE_URL") // 从 .env 文件读取
    }
    
    // 用户模型(NextAuth.js 关联)
    model User {
      id            String    @id @default(cuid())
      name          String?
      email         String    @unique
      emailVerified DateTime?
      image         String?
      posts         Post[]
      createdAt     DateTime  @default(now())
      updatedAt     DateTime  @updatedAt
    }
    
    // 文章模型
    model Post {
      id        String   @id @default(cuid())
      title     String
      content   String
      published Boolean  @default(true)
      authorId  String
      author    User     @relation(fields: [authorId], references: [id])
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
    }
  4. 生成数据库迁移:

    bash 复制代码
    npx prisma migrate dev --name init
  5. 生成 Prisma 客户端:

    bash 复制代码
    npx prisma generate
4.1.2 步骤 2:配置 NextAuth.js 认证
  1. 安装依赖:

    bash 复制代码
    npm install next-auth@beta prisma-adapter
  2. 创建认证配置(src/app/api/auth/[...nextauth]/route.ts):

    tsx 复制代码
    import NextAuth from 'next-auth';
    import CredentialsProvider from 'next-auth/providers/credentials';
    import { PrismaAdapter } from '@auth/prisma-adapter';
    import { prisma } from '@/lib/prisma';
    import bcrypt from 'bcrypt';
    
    // 初始化 NextAuth
    const handler = NextAuth({
      adapter: PrismaAdapter(prisma), // Prisma 适配器
      providers: [
        CredentialsProvider({
          name: 'Credentials',
          credentials: {
            email: { label: 'Email', type: 'email' },
            password: { label: 'Password', type: 'password' },
          },
          async authorize(credentials) {
            if (!credentials?.email || !credentials?.password) {
              throw new Error('请输入邮箱和密码');
            }
    
            // 查询用户
            const user = await prisma.user.findUnique({
              where: { email: credentials.email },
            });
    
            if (!user) {
              throw new Error('用户不存在');
            }
    
            // 验证密码(实际项目中密码应加密存储)
            const isPasswordValid = await bcrypt.compare(
              credentials.password,
              user.password || ''
            );
    
            if (!isPasswordValid) {
              throw new Error('密码错误');
            }
    
            return {
              id: user.id,
              name: user.name,
              email: user.email,
              image: user.image,
            };
          },
        }),
      ],
      session: {
        strategy: 'jwt', // JWT 会话策略
      },
      pages: {
        signIn: '/login', // 自定义登录页
      },
      callbacks: {
        async jwt({ token, user }) {
          if (user) {
            token.id = user.id;
          }
          return token;
        },
        async session({ session, token }) {
          if (token) {
            session.user.id = token.id as string;
          }
          return session;
        },
      },
    });
    
    export { handler as GET, handler as POST };
  3. 创建 Prisma 客户端实例(src/lib/prisma.ts):

    tsx 复制代码
    import { PrismaClient } from '@prisma/client';
    
    const globalForPrisma = globalThis as unknown as {
      prisma: PrismaClient | undefined;
    };
    
    export const prisma = globalForPrisma.prisma ?? new PrismaClient();
    
    if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
4.1.3 步骤 3:创建文章 CRUD API

创建 src/app/api/posts/route.ts(文章列表、创建文章):

tsx 复制代码
import { getServerSession } from 'next-auth/next';
import { authOptions } from '../auth/[...nextauth]/route';
import { prisma } from '@/lib/prisma';
import { NextResponse } from 'next/server';

// 获取文章列表(GET /api/posts)
export async function GET() {
  try {
    const posts = await prisma.post.findMany({
      include: { author: { select: { id: true, name: true, image: true } } },
      orderBy: { createdAt: 'desc' },
    });

    return NextResponse.json(posts);
  } catch (error) {
    return NextResponse.json(
      { message: '获取文章失败' },
      { status: 500 }
    );
  }
}

// 创建文章(POST /api/posts)
export async function POST(request: Request) {
  try {
    // 验证登录状态
    const session = await getServerSession(authOptions);
    if (!session?.user) {
      return NextResponse.json(
        { message: '请先登录' },
        { status: 401 }
      );
    }

    // 解析请求体
    const body = await request.json();
    const { title, content } = body;

    if (!title || !content) {
      return NextResponse.json(
        { message: '标题和内容不能为空' },
        { status: 400 }
      );
    }

    // 创建文章
    const post = await prisma.post.create({
      data: {
        title,
        content,
        authorId: session.user.id,
      },
      include: { author: { select: { id: true, name: true, image: true } } },
    });

    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { message: '创建文章失败' },
      { status: 500 }
    );
  }
}
4.1.4 步骤 4:创建前端页面
  1. 文章列表页(src/app/blog/page.tsx);
  2. 文章详情页(src/app/blog/[id]/page.tsx);
  3. 发布文章页(src/app/create/page.tsx,需登录);
  4. 登录页(src/app/login/page.tsx)。

发布文章页示例(src/app/create/page.tsx):

tsx 复制代码
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';

export default function CreatePost() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const router = useRouter();
  const { data: session, status } = useSession();

  // 未登录时重定向到登录页
  useEffect(() => {
    if (status === 'unauthenticated') {
      router.push('/login?callbackUrl=/create');
    }
  }, [status, router]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError('');

    try {
      const res = await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, content }),
      });

      if (!res.ok) {
        const data = await res.json();
        throw new Error(data.message || '创建失败');
      }

      // 跳转至文章列表页
      router.push('/blog');
      router.refresh();
    } catch (err) {
      setError(err instanceof Error ? err.message : '创建失败');
    } finally {
      setLoading(false);
    }
  };

  if (status === 'loading') {
    return <div className="container mx-auto px-4 py-8 text-center">加载中...</div>;
  }

  return (
    <div className="container mx-auto px-4 py-8 max-w-2xl">
      <h1 className="text-3xl font-bold mb-8 text-center">发布文章</h1>

      {error && (
        <div className="bg-red-100 text-red-700 p-4 rounded-md mb-6">
          {error}
        </div>
      )}

      <form onSubmit={handleSubmit} className="space-y-6">
        <div>
          <label className="block text-gray-700 mb-2">标题</label>
          <input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            className="w-full px-4 py-2 border rounded-md"
            placeholder="输入文章标题"
            required
          />
        </div>

        <div>
          <label className="block text-gray-700 mb-2">内容</label>
          <textarea
            value={content}
            onChange={(e) => setContent(e.target.value)}
            className="w-full px-4 py-2 border rounded-md min-h-[200px]"
            placeholder="输入文章内容"
            required
          />
        </div>

        <button
          type="submit"
          className="w-full py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
          disabled={loading}
        >
          {loading ? '发布中...' : '
相关推荐
JIngJaneIL1 小时前
基于Java在线考试管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
凌康ACG1 小时前
c++使用quickjs执行JavaScript
javascript·c++·quickjs
JIngJaneIL1 小时前
基于Java音乐管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
繁华似锦respect2 小时前
C++ 智能指针设计模式详解
服务器·开发语言·c++·设计模式·visual studio
接着奏乐接着舞2 小时前
react hooks
前端·javascript·react.js
郝学胜-神的一滴2 小时前
Linux进程创建的封装与设计模式应用:结构化分析与实践指南
linux·服务器·开发语言·c++·程序人生·设计模式
接着奏乐接着舞2 小时前
react redux 分组
前端·javascript·react.js
脾气有点小暴2 小时前
Vue2 与 Vue3 核心差异深度解析
javascript·vue.js
大猩猩X2 小时前
vue vxe-gantt 甘特图实现产品进度列表,自定义任务条样式和提示信息
前端·javascript·甘特图·vxe-ui·vxe-gantt