66-Data Fetching

image-20250914102512780

image-20250914102600085

创建新项目:npx create-next-app data-fetching-demo

67-Fetching Data in Client Components

使用https://jsonplaceholder.typicode.com/提供的api来获取数据。在client components里面获取数据,其实之前就学过,和react组件里面获取数据一样,只不过在nextjs中,需要使用use client将组件变为client components。

下面这段代码重点关注里面获取数据的部分。

可以看到,先出现Loading,数据返回后渲染了数据。

提示:

在client components里面获取数据,只在一些特定的情况下推荐使用,否则都应该在server components里面获取数据,哪些情况呢?

like when you need real-time updates, or when your data depends on client-side interactions that you can't predict on the server side,

68-Fetching Data in Server Components

image-20250914105709626

在server components里面请求数据,就像写普通的js代码那样。

可以看到数据正常显示了:

针对server components的数据请求,react有优化:

image-20250914110744940

react will deduplicate fetch requests with the same URL and options。react 会将具有相同URL和传参的fetch请求去重,使用第一次请求获取的数据。

image-20250914110118082

这意味着可以在component tree的任何位置请求数据,而不用担心重复的网络请求。因为react针对相同的URL和传参,只会执行一次fetch操作,并在同一渲染传递中的后续调用中重复使用结果。

这样做的好处是什么呢?可以让我在需要data-fetching的时候就使用它,而不用集中处理数据请求并通过props来传递数据。

69-Loading and Error States

在client components中,我们使用useState来定义loading和error的,那在server component里面,不能使用useState,怎么展示loading和error的状态呢?

只需要定义loading.tsx和error.tsx这两个文件即可,之前已经学过了,这里丰富一下内容。

image-20250914111456593

为了查看loading的效果,在users-server/page.tsx里面加一个延迟,如果不会就看前面学习的内容。看一下效果:

为了查看error的效果,将请求地址修改为错误的地址,查看:

70-Sequential Data Fetching

image-20250914112454130

并行请求是我们常做的,而顺序请求在一些情况下是必须的,比如说要先获取某个组织,然后获取某个组织下的人员列表。

image-20250914115603534

下面专门讲解顺序请求。案例需求:获取所有博客,然后获取每一个博客里面的作者信息。

image-20250914114052522

先创建博客列表页面:

查看效果:

下一步就是将每一个post里面的author name获取到,使用/users/1这个接口。怎么获取呢?这种写法之前还真的没有见识过,是编写一个author.tsx组件,父组件传递userId过去,再请求数据。

为什么可行呢?因为父组件里面使用了async...await,userId是肯定有的,那么在author.tsx里面请求数据就没有问题。

在父组件中使用author.tsx:

image-20250914130959160

可以看到名字都显示出来了。

image-20250914130927286

这里模仿一下获取name网速慢的情况,在author.tsx里面添加延迟操作await new Promise((resolve) => setTimeout(resolve, 3000));

可以看到把整个页面的渲染都阻塞了,这时候就要想到使用Suspense来解决卡顿问题:

image-20250914134808385

效果:

可以看到,虽然接口被阻塞了,但是其余的页面及时渲染了,这样的效果很好。

 

这个免费的API接口,浏览器可以使用,但是server component里面不能使用,找不到原因。放到API FOX上测试,显示:"Connection was forcibly closed by a peer"。链接被对方强制关闭了。

应该是有请求次数的限制,学习的时候老是出不了效果怎么办?还是自己做一个后端服务吧。我做了一个jsonplaceholder的服务端,一切都好起来了。

71-Parallel Data Fetching

image-20250914120334634

这个案例里面,展示同一个用户的posts和albums,这样请求/posts?userId=1/albums?userId=1,那么当进入到用户详情页面时,这两个接口可以同时发起请求。这就是parallel data fetching的意义。

查看效果:

现在为每一种数据请求添加一个延时,并且添加loading效果。

 

image-20250914184054538

image-20250914184109355

这是老师说的,但是后端能不能接受还需要询问。

 

这里有另外一点需要注意:

因为这里是server components,所以浏览器的network面板里面没有这两种数据请求,我是说怎么找都找不到呢。如果要调试的话,只能在terminal里面输出来看。

72-Fetching from a Database

image-20250914184350652

这节课开始学习如何从数据库获取数据。

image-20250914184645777

参考文档:https://www.prisma.io/docs/guides/nextjs

下面的步骤是在nextjs中添加prisma和sqlite,这些步骤刚开始不熟悉是很正常的,多看、多用就会了。

1、npm i -D prisma安装prisma。

2、使用sqlite初始化prisma,npx prisma init --datasource-provider sqlite。这个命令报错。

image-20250914190822409

解决办法:执行这个代码:npx prisma init。会在项目的根目录生成一个prisma/schema.prisma的文件,修改里面的内容如下:

重要的就是修改datasource db里面的内容,provider改为sqlite,url就是db文件的地址,注意generate client里面的output属性,需要去掉。

问了grok。

默认行为:如果不指定 output,Prisma 会自动将生成的客户端代码放到 node_modules/.prisma/client 目录中。这是最常见的设置,尤其在标准 Next.js 项目中。它不会污染你的源代码目录(如 src),并且在运行 npx prisma generate 时会自动处理。

image-20250915085244825

这一步没有安装sqlite,难道不需要安装吗?

image-20250915085616999

3、npx prisma migrate dev --name init,生成prisma迁移和客户端。

prisma使用迁移来同步模型到数据库。如果以后新增、修改、删除了schema.prisma里面的modal,需要使用这个命令来更新。

这一步执行之后,会在prisma文件夹里面生成这些文件,生成了这些文件之后,下一步就可以使用了。

注意开发过程中要将生成的.dev.db文件放入到.gitignore中去,因为数据库文件一般不放入git。

image-20250915090003527

4、创建src/prisma-db.ts文件,在里面编写数据库操作的代码。不要管里面的await new Promise的延时代码,这些只是为了模拟的:

5、在server components里面使用prisma。直接使用定义好的方法。

效果:

这里有一个卡顿,是因为加了await new Promise。实际上是很快的。

这里的从数据库获取数据和我想象的不太一样,或许这就是以前的前后端不分离的做法,但是我之前并没有见过。

我想象的还是编写后端接口,前端使用axios或者fetch来调用接口。没有想到这里是直接使用方法名了。

实际上还是可以先定义route.ts,在里面使用prisma,然后在组件里面调用接口。不过直接调用方法名是更简单的做法。具体还是看项目需求。我肯定是直接使用方法名了。

2025.12.01

不要直接使用方法名,还是要使用route handlers,除了纯GET请求之外,其余的都要使用route.ts来定义接口,因为nextjs为route提供了很多优化项。

2026-01

可以直接在server components里面操作数据库来获取数据,GET方法是没有问题的。如果遇到create、update、delete这些操作,则要使用server actions。

 

2025-10-22 今天想专门学习prisma,看到web dev simplified这个老师讲解的配置prisma的过程,和Codevolution的一致,而且配置的没有问题。我决定重新使用Codevolution老师在课上的配置方式来试一下,结果真的可以。搞懂了prisma定义的相关规则之后,配置起来就没有问题了,直接参考prisma的文档:https://www.prisma.io/docs/getting-started/prisma-orm/quickstart/sqlite,这个文档已经够清楚了。

73-Data Mutations

data mutations是什么意思?数据突变?可以理解为数据CUD的操作,相当于nextjs提出的一种专有名词,显得有逼格。

image-20250915093043370

image-20250915092404648

 

image-20250915092547371

首先让我们看一下在react里面,怎么实现数据的CUD。

新建app/react-form/page.tsxapp/react-form/api/route.ts

可以看到,page.tsx里面定义的变量有很多,不过整个流程看起来确实还是蛮清晰的。

可以看到有一个小卡顿,这是因为接口里面写了await new Promise的原因,后面会解决这个问题。

74-Forms with Server Actions

server actions适合<10 个字段、无文件、无条件逻辑这样的场景,如果是复杂场景,就直接使用client components吧,不要成为了nextjs的“适配器”,不值得。

几个概念需要注意:

 

image-20250915094532270

image-20250915094556786

server actions的约定如下:

1、在async function里面的顶部(这里老师的英文说的不准确),使用"use server";来标记这个函数是server action。

2、在一个单独的文件里面,顶部使用"use server";,来标记这个文件导出的所有函数都是server actions。

3、server action函数,要写在form标签的action属性上。

有了server action,可以做什么呢?下面以一个案例来说明,还是新增product的需求。

新建products-db-create/page.tsx,从react-form/page.tsx里面复制return的内容,把里面的onSubmit、onChange事件,loading变量都去掉,变成这样:

在组件内创建server action函数,在函数内部的顶部使用"use server"来标记。

下一步,将函数赋值给form的action属性,将函数和form连接起来。

当有人提交表单时,createProduct函数会自动接收form data作为参数,将参数接收之后,就可以直接调用prisma的方法。

注意:为什么formData里面可以获取到具体的form-item的值?因为无论是input还是textarea,都指定了name属性,就是根据name属性获取的值。

查看效果:

可以看到提交成功了。

以下是server actions的好处:

image-20250915102711530

下面就第4点好处来说明,打开/products-db-create页面,在Sources面板里面,使用ctrl+shift+p,输入disable javascript禁用js。

image-20250915102850066

然后查看是否能够正常提交表单。

可以看到能够正常提交表单。使用ctrl+shift+p,输入enable javascript,启用js。

下面是server actions的一些好处,更加复杂的server actions概念,我放到了一个单独的文档里面去了,查看即可。

从第73节课可以看到,client component里面操作表单还是蛮复杂的,要定义和处理很多变量。那么我有一个疑问:与传统方法相比,server actions做了哪些,然后程序员就不需要做了?

 

  1. 无需手动编写 API 路由 (Endpoints)
  1. 无需处理 JSON 序列化与反序列化
  1. 无需手动同步数据状态 (Cache Invalidation)

这是最省心的一点。

  1. 降低了“类型安全”的维护成本
  1. 不用定义很多变量了, Next.js 给 <form action={...}> 做了特殊增强,会自动把 FormData 对象传给 Action函数。
  2. 可以从useFormStatus / useActionState获取到loading状态,可以从 useActionState 获取到error信息。代码更简单了。
  3. 如果浏览器的js被禁用了,server action还是可以执行,因为它运行在server上。

老师的表单比较简单,但是我一看到表单,我就想到了上传文件这种复杂场景,还有什么场景?

 

除了最常见的「文件上传」,很多场景都很复杂,而且用纯原生 Server Actions 写起来都会非常痛苦(错误处理、验证、状态管理、并发控制一大堆 boilerplate)。

这个不用太担心,就按照下面的标准来做,复杂的表单直接问AI该使用什么库即可。

75-Pending State with useFormStatus

form提交的时候,我们想添加按钮禁用功能,避免用户的重复提交。这时候需要使用useFormStatus方法,可以获取到最近一次的form提交的相关参数。

image-20250915103428661

这节课主要专注于里面的pending参数。

image-20250915103449646

因为useFormStatus是react hook,所以只能在client components里面使用。但是为了使用这个hook,将整个form组件变为client component,显然是不行的。

可以将submit按钮抽出来,成为单独的client component。为什么可行呢?因为useFormStatus返回的是最近一次form的提交,与具体的form其实是解耦的,所以submit组件里面能够获取到准确的信息。而且button点击后会触发form的action,其实这也是与form解耦了。

上面这段解释非常重要,因为我无论是使用jQuery还是vue,我都是直接在button上面绑定事件的,button和form其实是不能分开的,但是react里面就不一样了。

products-db-create/page.tsx里面的按钮替换为这个组件<SubmitButton />,查看效果:

可以看到,点击提交之后,按钮就变为不可点击了。

76-Form Validation with useActionState

image-20250915105427306

这节课告诉我们怎么使用useActionState,但是这节课最终没有完全解决问题,要等到下一节课才能完全解决。

先看怎么使用useActionState

1、定义错误类型、form表单的状态类型(只需定义里面的errors属性)

2、在组件中定义初始状态,并使用useActionState关联form action函数和初始状态

3、在form action函数中,验证表单数据,并设置错误消息。如果errors对象有属性值,那么就返回errors对象

4、useActionState返回3个参数,state是表单当前状态,里面有errors;formAction,是一个form action函数,需要使用这个函数替换掉createProduct;isPending,表单提交的pending状态。

因为state里面有errors对象,所以在jsx代码中,添加警告的代码。

这节课中,先不使用之前定义的Submit按钮组件,使用普通的按钮,将isPending状态加上去。第78节课会详细说明二者的区别。

此时我们会遇到一个错误:

image-20250915113651577

useActionState这个react hook只能用在client components中,那么我们将这个组件变为client components之后,又有新错误:

image-20250915113800102

It is not allowed to define inline "use server" annotated Server Actions in Client Components. To use Server Actions in a Client Component, you can either export them from a separate file with "use server" at the top, or pass them down through props from a Server Component.

不能在client components里面使用"use server",提示信息还告诉我们怎么解决:要么将代码单独抽离出来;要么当作props传递过来。

下节课中我们使用第一种方法来解决问题。

useActionState 的执行原理可以看作是 React 在前端维护了一个“状态循环机”。它不仅仅是一个简单的状态钩子,而是 React 19 为了深度集成 Server Actions 而设计的状态机调度器

我们可以从以下三个维度来拆解它的底层逻辑:

  1. 核心模型:Reducer 模式的延伸

如果你用过 useReducer,你会发现 useActionState 的参数结构非常像。它的核心原理就是:(旧状态 + 输入参数)=> 新状态

 

  1. 执行流水线(Step-by-Step)

当你点击按钮触发 actionTrigger 时,React 内部发生了以下链式反应:

① 开启 Transition 轨道:

React 内部会自动开启一个 startTransition。这就是为什么它能自动提供 isPending 状态的原因。它告诉 React 调度器:“接下来的更新是低优先级的,不要阻塞主线程。”

② 快照当前状态:

React 会捕获当前的 state 作为 prevState。

③ 跨越“服务器边界”:

React 调用你传入的 Server Action。由于是在 Transition 中执行,React 会等待这个异步函数(Promise)完成。

④ 状态合并与 UI 更新:

当服务器返回结果后,React 会执行一次“状态分发”

 

3、为什么它需要第一个参数是 prevState

你可能注意到了,Server Action 在配合 useActionState 使用时,函数的第一个参数必须是 prevState

原理在于“连续性”:

Server Actions 是无状态的 HTTP 请求。为了让 Action 知道上一次发生了什么(比如:这是第几次尝试?上一次处理到哪一步了?),React 要求你把状态传回给服务器,服务器处理完后再传回给前端。这形成了一个状态闭环。

 

4、这里的“魔法”:序列化 (Serialization)

useActionState 最神奇的地方在于它处理数据的方式。

5、总结:它解决了什么技术痛点?

在没有这个 Hook 之前,我们要处理一个异步交互需要维护 3 个独立变量

  1. data (存储结果)
  2. loading (手动设 true/false)
  3. error (手动 try/catch 捕获)

useActionState 的原理就是将这三者合为一体,并挂载到 React 的 Transition 并发调度机制上。 它是真正的“全栈状态同步器”。

77-Separating Server Actions

我们抽离server action函数到一个单独的文件中去,这样就可以保证使用use server没有问题。哪部分用到了use server,就抽离那部分。

创建src/actions/products.ts,这文件的顶部使用"use server";directive,将整个文件的内容都变为服务端使用。

products-db-create/page.tsx里面引入ts类型和函数,使用即可。

image-20250915114759457

useActionState的第一个参数有个报错,这是因为第一个参数的函数createProduct里面,第一个参数是prevState,加上之后就不报错了,这里的prevState暂时不使用。

image-20250915120824726

测试一下:

可以看到必填项的校验触发了。