会了 React 和 HTTP 协议,就等于会了 Next.js

2 阅读8分钟

润物细无声。这就是好的技术栈,让你不知不觉就用上了。

大家好,我是双越老师~

背景

3 月初宣布做一个 Node 全栈 AIGC 知识库项目 划水AI (类似 Notion AI 和协同编辑),受到很多同学的关注和支持。

项目背景和功能可看这里 前端转全栈: Next.js + ChatGPT 开发 AIGC 知识库(AI 写作) 欢迎围观~
想围观、学习、参与开发的同学,可以私信我~

项目计划 4 月正式开始,现在我正在做一些调研和准备工作。项目的核心技术选型是 React 和 Next.js 。某些模块也会使用 Nest.js ,扩充项目的技术广度。

本文是 Next.js 的介绍和入门,主要给已经参与了 划水AI 项目的同学,让他们快速学习 Next.js 。源代码在文章末尾。

为何选择 React 和 Next.js

因为简单。

上周我写过一篇文章 【长文】只会 Vue 不会 React ?22 点证明 React 比 Vue3 更简单 ,从多个方面介绍了 React 其实非常简单,会 JS 就会 React 。

其实 Next.js 也一样,它没有太多新概念,只要你会 React 和 HTTP 协议,你就能轻松入门 Next.js ,不需要你熟悉很多 Node.js 的知识。不信可以阅读完本文,如果没有理解上的障碍,那就是已经入门了。

PS:可以对比一下隔壁的 Nest.js ,上来就是一堆概念(Module Service Controller...),代码里还有各种 @ 装饰器语法。很多前端同学都没见过。

创建项目

参照 Next.js 文档 一键安装。

image.png

PS:推荐使用 tailwindcss ,这也是 Next.js 推荐的 CSS 方案,很多 example 都会用它

运行 npm run dev 然后浏览器访问 http://localhost:3000 即可。

修改首页内容

源码中,打开 src/app/page.tsx ,这是首页的代码,都是 React 组件,可以随意修改它。

想要修改网页 head 内容(即 metadata),可在 src/app/layout.tsx 中修改。

// 改为你自己的标题、描述,或者扩展其他的 metadata
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

创建一个页面

新建一个目录 src/app/blogs ,然后在其中新建一个文件 page.tsx ,代码如下

// src/app/blogs/page.tsx
export default function Blogs() {
  return <div>
    <h1 className="text-3xl">Blogs</h1>
    <p>博客列表</p>
  </div>
}

然后在浏览器访问 http://localhost:3000/blogs 即可看到效果。

image.png

你还可以在 blogs 下面再继续新建目录和文档 src/app/blogs/test/page.tsx ,然后浏览器访问 http://localhost:3000/blogs/test

对,这就是 Next.js 的路由规则,非常简单。你想定义怎样的路由,你就创建相应的目录,然后在文件夹中新建一个 page.tsx 返回一个 React 组件即可。

image.png

这样的路由规则比 React-router 还要简单,还要容易理解。

动态页面

创建目录 src/app/blogs/[id],在其中创建 page.tsx 文件,可以定义动态页面。React 组件中可以使用 params.id 获取参数。

image.png

浏览器访问 http://localhost:3000/blogs/123 即可看到效果 (其中 123 是 id,自行修改)

image.png

服务端组件

React Server Component 服务器组件 —— 运行在服务器端的组件。

不用管这些概念和术语,因为你已经开始使用服务端组件了,上文的三个页面,都是服务端组件 —— 不知不觉就让你用上了 —— 这就是简单的技术和框架,随风潜入夜,没有任何门槛。

为了能更好的体现出“运行在服务端”,我们可以继续加一些功能。让我们修改一下 src/app/blogs/[id]/page.tsx 这个页面

首先,给组件函数增加 async ,然后创建一个函数 MockGetBlogDetailFromDataBase 模拟从数据库获取博客详情,最后在组件函数中调用获取数据,并在组件 JSX 中返回。

// 服务端组件,在 node 服务端环境执行(无法在客户端浏览器执行)

// 模拟从数据库获取博客详情
async function MockGetBlogDetailFromDataBase(id: string) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        id,
        title: `博客标题${id}`,
        content: '博客详情 博客详情 博客详情',
      })
    }, 1000)
  })
}

export default async function OneBlog({ params }: { params: { id: string } }) {
  const blog = (await MockGetBlogDetailFromDataBase(params.id)) as any

  return (
    <div>
      <h1 className="text-3xl">{blog.title}</h1>
      <p>{blog.content}</p>
    </div>
  )
}

以上这些代码,async function 查询数据库,只能在服务端去执行,只能服务端组件去做。最后执行的结果也是显而易见的

image.png

先不要什么概念、好处、区别啥啥的,就说上面这些代码和逻辑,你能不能理解?能的话继续,不能给我留言评论。

客户端组件

纯前端使用 React 开发的就是客户端组件,即代码在客户端(浏览器)执行。客户端组件不能使用 async function 更不能去直接查询服务端数据库。

Next.js 中默认组件是 Server Component 服务端组件,如果你想使用客户端组件,必须给一个标志 'use client' 让 Next.js 知道你的意图。下面的 demo 中有。

有点麻烦啊,为啥要用客户端组件呢,多使用服务端组件不好吗?—— 当然不行,因为服务端组件有很多限制。

当你使用 useState useEffect onClick 等 API 时,即当你需要有一些用户交互,需要使用浏览器 API ,需要有组件的更新和变化时,就需要客户端组件。

服务端组件就是一个“直肠子”直接输出,没那么多小动作。

例如,我们修改一下 src/app/blogs/page.tsx 页面,显示 Blog 列表,再增加“删除”功能。 其实这些代码都是非常基础的纯前端 React 代码,大家不看也罢。

'use client'

import { useState } from 'react'

export default function Blogs() {
  const [blogs, setBlogs] = useState([
    { id: 1, title: 'Blog 1', content: 'Content 1' },
    { id: 2, title: 'Blog 2', content: 'Content 2' },
    { id: 3, title: 'Blog 3', content: 'Content 3' },
  ])

  function handleDelete(id: number) {
    setBlogs(blogs.filter((blog) => blog.id !== id))
  }

  return (
    <div>
      <h1 className="text-3xl">Blog List</h1>
      <div>
        {blogs.map((blog) => (
          <div key={blog.id} className="border p-2 my-2 relative">
            <h2 className="text-xl">{blog.title}</h2>
            <p>{blog.content}</p>
            <button
              className="underline absolute top-4 right-3"
              onClick={() => handleDelete(blog.id)}
            >
              删除
            </button>
          </div>
        ))}
      </div>
    </div>
  )

由于用到了 useState onClick 所以必须使用客户端组件,代码第一行标记 'use client'

创建一个服务端 API

客户端组件没办法直接查询服务端数据库,那就得使用 axios 或 fetch 等去 ajax 请求服务端数据,这些是前端 JS 的基本能力。

Next.js 作为 SSR 服务端框架,它也提供了开发服务端 API 的能力。新建一个目录 src/app/api/blogs 在其中新建文件 route.ts ,然后输出一个 GET 方法,返回一段 JSON

image.png

浏览器访问 http://localhost:3000/api/blogs 即可看到效果。

image.png

和新建页面一样,这都是 Next.js 路由规则。页面使用 page.tsx 文件,API 使用 route.ts 文件。支持 GET POST PATCH DELETE 等所有 HTTP method 。

export async function GET() {
  /* 省略已有代码 */
}

export async function DELETE(id: string) {
  // 在数据中删除 id 对应的 blog ...

  return Response.json({ errno: 0 })
}

有了服务端 API ,刚才的客户端组件也可以使用 axios 或 fetch 来请求数据了,这个太基础了,就不再演示了。

Server Action

无论是服务端组件,还是客户端组件,输出的都是 UI ,都可以和用户交互。当用户点击一个按钮,或者提交一个表单,都需要提交数据到服务端。

纯前端考虑的到的方案,就是 ajax POST 到服务端。现在 Next.js 给了你一个更简单的选择 —— Server Action —— 直接调用一个函数即可(让你忘掉 ajax) 。当然了,如果你不喜欢,可继续用 ajax POST 。

对于服务端组件,可通过 'use server' 定义一个函数为 Server action 然后直接调用。

image.png

对于客户端组件,需要你单独创建一个 JS 文件,标记为 'use server' ,并 export 一个函数

// src/blogs/action.ts

'use server'

export async function delBlog(id: number) {
  // 调用数据库,删除 id 对应的 blog
}

然后在客户端组件 import 这个函数,直接调用即可。

image.png

PS:虽然写法上是一个函数调用,但 Next.js 还是会把它处理为一个 POST 请求,所以它只是一个语法糖,方便我们学习和使用。

另外,Server action 用的比较多的地方还有 form ,但我觉得不如上面两个例子来的直接一些。

你已经入门了

恭喜你,能看到这里,你已经入门了,核心概念就这些。你完全可以去做一个增删改查的 demo 。文本 demo 的源代码 github.com/wangfupeng1…

虽然还有很多 Next.js 高阶应用和优化方案没有讲到,但那些都是锦上添花的东西,去看文档查找即可。

接下来强烈建议你再去看看 Next.js Learn 页面,跟着一步一步做出这个小项目。我觉得这是讲解最好的 Next.js 入门项目,毕竟是官方出品。英语不熟练的可以直接浏览器翻译。

加入【划水AI】项目研发团队

如果你想进一步学习 Next.js 框架,如 shadcn-ui 、登录校验、ORM、数据库、Serverless 、线上统计和监控等。欢迎加入 划水AI 项目研发小组。

除此之外,我们还会深入研发富文本编辑器、协同编辑、AIGC 能力(AI 写作、AI 文字处理、AI 智能提示等)。有兴趣私聊我~