lazyczx 最近的时间轴更新
lazyczx

lazyczx

V2EX 第 465939 号会员,加入于 2020-01-20 01:04:44 +08:00
lazyczx 最近回复了
@epiloguess

把取件码搞错成密码了。。

再来一次

https://airportal.cn/81745/WQIRw8VCNn 复制链接到浏览器打开

123456
@epiloguess

老哥聊了那么多,可否给个微信好友位,为了不过度暴露自己的个人信息,我弄了只能下载一次的一个文件,哈哈反正个帖子应该也没什么人看到这么后面!

https://airportal.cn/519534/Fwr351S2E5

密码是 519534
@epiloguess

我看了老半天那个 pr ,不知道的东西太多了,还好有 chatGPT 的语境分析和解释( openAI 真的牛),我大概知道了,就是这个 postpone 是 react 用来解决 infinite promise 的,然后是通过抛出错误解决的,用来处理静态组件到动态组件的退化(我感觉这个描述和我们发现的问题高度相关)。。

我昨天试了一下你说的(一个组件上 noStore ,其他的组件 noStore + cache )把 .next 删了重新 build ,确实可以,后来我也发现我之前 build 成功的结果,是因为本地已经有缓存了,所以也许那个 cache 直接就没执行,因此也轮不到莫名其妙哪里来的 validate 0 和静态组件发生冲突了。

你后面的源码分析里面是不是说是因为那个动态的组件的缘故,然后导致数据库的请求全部默认 validate 0 了,也就是不缓存的意思,这样和静态组件冲突了?

确实就像你说的,静态的部分如果用了 db 就必须 noStore + cache 才可以。

我看了你的对源码的分析之后,今天去尝试了一下怎么 debug 本地的 npm 仓库(就是在仓库里打断点),我以前在 Java 的项目里,这个是很简单的。

我做的时候,用的是 npm link ,下了 nextjs 仓库然后 link 替换掉了 dashboard 项目里的 next ,但是运行命令的时候提示 next 找不到,于是我全局安装了 next ,但是运行各种命令都报错,报什么 m...interface 什么找不到,我去 next 里 npm i 了也没用(虽然好像默认是不需要自己跑过去 npm i 的吧?)。

然后搞了半天最后还是失败了,只能做到 debug 自己写的代码,不能 debug 库里的,用 symlink 连接 next 直接跑不起来,我现在都怀疑这个方法是不是根本不可行。

你知道怎么 debug 公共库里的代码吗?就是在公共库的代码上加断点这种,比如 debug next 的代码,或者 react 的代码。

然后,我重新 i 了 canary 50 ,然后 build 的时候提示出来的是 52 版本,然后我昨天试过的代码根本没变,就 build 失败了,还是那个 validate 0 ,should bail out 的错误,我这里先考虑是版本有问题,先把版本弄正确了再说。

然后我把 node_modules 删了,再把 global 的 canary 删了,再试了一下 build (确认原先安装的 next 已经删除),然后看到命令找不到之后,又 npm i 了结果出来的还是 52 ,然后删了好几次 node_modules ,npm i 下下来的都是 52 ,而我的 package.json 里的版本是 50 ,但是去 node_modules 里看的还是 52 。

然后我把版本改成 49 ,这次下的是 49 了,然后又改成 50 ,就成功把包改成 50 了,这什么情况,好诡异。。。。。。
@epiloguess

我实验了很久,总体感觉这个 cache 和 nostore 一起在 ppr 里用就是会有很多预期之外的问题,我有次尝试过把三个组件的 cache 都用上,然后各自加 nostore 发现只有特定的两个组件加上 nostore ,或者全部加上才不会报错,当然这个可能在这个网页的功能角度来看里是没意义的。后来我尝试用功能实现的角度去真正使用这个 ppr ,我把 cache 全部去掉,然后在一个组件上使用 nostore ,报错了,而我的预期是没加 nostore 的两个静态渲染,加 nostore 的就动态,但是。。反正是报错了。

然后我最后发现,要成功,只能把两个想做静态的组件在原来的基础上用上 cache ,这样页面才会如预期,只有一个是动态渲染的,其他俩静态(最终只有一个地方用了 noStore ),这应该就是你说的 cache 一把梭了。。。

然后我试了你的那个改源码的办法,出意外了。。。没办法通过改源码,把我上面的 俩 cache 给省掉啊。不加 cache 还是会报错。

不过你是真滴强,熬夜看源码。。我感觉面对这种 beta 的功能,连钻研源码的兴趣也没有,甚至我昨天之前都不知道前端项目咋看源码,我 ctrl 点进去,全是 ts 定义文件,昨天还问了下 chatgpt ,它推荐我去下代码仓库下来看。。我学习这些,都想的是有一个相对正确的 overview 就可以了,然后会使用,因为我觉得面向接口编程大多数情况下是挺好的,毕竟别人写的代码是无限多的,如此细粒度的学习仿佛让我感觉我在虚度时间一样,总之我觉得钻研细枝末节的实现的成本好大,但是这也导致我碰到问题了,如果没有他人/搜索引擎帮助,很难自己钻进去,然后获得一些成果。但是总体上,我觉得会看源码是个很厉害的事情,能通过看源码,针对性地找到自己想要的答案,这件事很厉害。但是我对于怎么做到这样,怎么样让这件事变高效很迷茫,也对做这件事情的“边界”感到迷茫,是一旦有好奇心就去看,看的话要看多少,要看哪些之类的。。在这样的状态下,更不用说让我去看一些功能还未成熟的源码。。。然后看到你还会熬夜看源码,属于是感到一个天一个地的差距了~_~

如果可以,希望听到你的感想!
@epiloguess

> 这样的话,在 unstable_cache 缓存的函数中使用 noStore ,内容就会被静态渲染了,你去开发者工具里查看第一个接受的 document 就能看到

这个是在 PPR 开启的情况下吗?

你的意思是没有使用 "unstable_cache 前的 noStore()" 的情况下吗?

那就是变成我说的会报错的 组合 1 了。

我刚刚升级到 50 版本 ( from 49 )测试了,还是会报错呀。

如果你说的是 ppr 未开启的情况下,那肯定是会开启静态渲染了,因为 cache 里的 noStore 似乎没有直接作用于 next route (不知道我的表述对不对)。

使用 unstable_cache 的话,也没有在 unstable_cache 前使用 noStore ,在 PPR 下会直接报错( validate 0 和 unstable_cache 的冲突);不在 PPR 下的话,本来就会静态渲染呀,我好像没有否认过这一点。

和这点有关的我先前的回复:

> 我刚刚去把版本换成 canary without ppr 测试了一下,我用了 noStore 在 API 方法里,然后使用 cache 把 API 包裹起来,生成树还是静态的(注意我没有使用你上面回答里的推荐做法,即把 noStore 放在组件里,放在 cache 方法上方,不知道是不是这里有误解。。)。
@epiloguess

概念讨论:

因为很多文档我还没有读过(不过我后面会认真读的,尤其是你发出来的文档! owo ),所以我这里就是大胆秀一下我的理解,如果有不对的地方,希望能够获得宝贵的指正!

动态组件是 PPR 开始才有的,之前是只有动态 route 。它是在静态渲染页面里面动态的部分,每次 request 都会重新渲染。

在客户端的 RSC 和 RSC 本身,这个有点混淆不懂。。我感觉 RSC 这个东西(我没看过文档,根据感受瞎猜一下 hh )就是把以前应该在客户端的操作挪到服务端,由服务端做好之后,直接把结果( render 好的服务端组件)发回给客户端。而且最重要的是它可以和 客户端组件 配合,客户端组件变化的时候可以把变化传回给服务端,然后服务端组件 render 之后再返回给客户端,隐藏了数据的处理。至于在客户端的服务端组件,好像服务端组件,如果在客户端就无法使用很多客户端才能提供的东西,比如 cookies 。

静态导出应该是整个网站都是静态站吧,每次想要更新网站的内容,只能 build ,而静态渲染因为有 revalidation ,所以不一定要 build 。

在没有 ppr 之前,路线只有静态渲染,动态渲染,流式渲染

静态渲染是在构建时和 revalidation 时渲染一次。

动态渲染是 request 请求发过来就渲染一次。

流式渲染是哪个部分先渲染好了,就先发过去哪个部分,让 HTML 不间断地更新,ready ,实现方法是分别在一个 <Suspense> 中包裹每个 RSC ,并且把 data fetching 扔到每个 RSC 内自己做,自己管理自己是否 ready 。

---

另外,你说的这个:

> 好在官方可能考虑过这一点了,如果你在 unstable_cache 缓存的函数中声明 noStore,那么在 nextjs 看来,组件仍然是可以被 ppr 的,不会受到 nosStore 和 revalidate0 影响了

我去 canary with ppr 测试了:

会报错的组合:

1. 有 cache

noStore: 在 cache 内 or 完全没有

2. 无 cache

noStore: 没有

不报错的组合:

1. 有 cache

noStore: 在 cache 外,在 cache 前(**这里就是 PPR 动态组件使用 cache !**,这时候可以使用 Suspense 在 PPR 做 Stream 其他动态的内容,我在 revenue 那里使用 noStore 再使用 unstable_cache 缓存了 api 返回值,然后又使用 Suspense 包裹了一个空调了一下 revenue API 的组件,发现 revenue API 每次刷新只被调了一次,说明 PPR 里的缓存起效了!)

2. 无 cache

noStore: 在 API 方法内外均可,只要在 API 方法里 try catch 之前即可。

ppr 下,有 revalidate 0 ,next 会不知道我们想要什么( next: 为什么你使用了 revalidate 0 却不显示声明 noStore 啊啊啊迷惑死,大概它是这样的心理 = =),因此必须在 cache 外(如果有 cache )使用 noStore ,明确告诉要使用 ppr 。

因此我认为,应该要在 cache 语句之前声明 noStore 才有用,在 cache 内部使用 noStore 没用,因这样好像影响不到 cache callback 的外部的组件。

---

排版问题,确实,v2 为啥不在回复里应用 markdown 引擎。。应该不麻烦吧这个。。难道就是鼓励简短的无结构化的回复??
@epiloguess

想说点无关的话,希望不会 bother

不知道是不是因为喜欢潜水久了,突然有解决不了的问题,然后被逼的开始和人交流,带来的感觉真的很不错,感觉自己现在甚至有点像什么人来疯那种心态了(开玩笑 hh )。之前也交流过后端的问题在 v2 ,也有经验者帮忙,感觉到自己真的处于一个互帮互助的社区,这种感觉真的很棒!而且很多问题问 gpt 感觉问不出所以然(可能语言组织还不够),尤其是这种比较新的问题( chatGPT 3.5 数据库才更新到去年 4 月份,然后国内 gpt 又感觉会有很多幻觉),还是交流起来开心哈哈哈。。虽然打字组织语言和想法真的挺累的,但是感觉这也是在锻炼自己薄弱的环节,这感觉也很好!!
@epiloguess

关于第一点,我确实测试的是非 canary ,我以为这个 canary 是 unstable 的,就没想这个是正式版 haha 。。

---

关于和 PPR 有什么关系:我当时就是感觉如果假设页面上只有两个调 API 的组件,其中一个 A 组件调 API 的时候用 cache ,另一个 B 调 API 的时候用 dynamic ,那就相当于 PPR ,因为 A 组件在不 revalidation 的情况下就一直是静态的了,而 B 组件会变化,这和我心里想的部分预渲染的定义相符。可能到教程里的例子,就是把除了 noStore 的,其他的 API 调用都用 cache 包起来,就相当于实现了 canary 里的 PPR 的功能了,即默认其他的都是静态。

也可能是 cache 的文档里有 revalidation 这个术语,让我想起了教程里 chapter 10 有这句话(看,这里也有 revalidation ):

> Partial Prerendering leverages React's [Concurrent APIs]( https://react.dev/blog/2021/12/17/react-conf-2021-recap#react-18-and-concurrent-features) and uses [Suspense]( https://react.dev/reference/react/Suspense) to defer rendering parts of your application until some condition is met (e.g. data is loaded).
>
> The fallback is embedded into the initial static file along with other static content. At build time (or during **revalidation**), the static parts of the route are *prerendered*, and the rest is *postponed* until the user requests the route.

​ 所以我以为它们也许是 通 的。

---

> 你需要确保的是,如果函数都加上 unstable_cache 了,你这个问题应该就不会出现了吧。

这个我觉得应该是不会了,毕竟我当时测试的时候,加上了 unstable cache 之后,如果没有设定 revalidation 的话,不管怎么操作,都不会 rerender 了,就像是一个动态页面里独立出来了一个静态的玩意儿。所以用了这个应该是绝对不会出来这个问题。

---

> \> 关于第 5 点,我不知道你是怎么测试的,但是我测试的情况下,如果 API 方法里使用了 noStore ,然后把这个方法作为 cache 的 fallback 的话,确实不会退出静态生成。build 的时候显示的 tree 也显示页面仍然是 static ,
>
> 你这个是在什么条件下测试的?

是非 canary 下测试的呀,就是 noStore 只加在 revenue api 方法上,然后用 `cache(()=>getRevenue(), ['revenue'])`。发现 router 树 dashboard 后面的符号是 圆圈。而且用页面的时候 revenue 也没有被调用(除非有 revalidation )。

> 在正式版/canary without ppr 中,
> 这个 noStore 是不是 cache 的 callback,都没有关系,只要你这个路线中,任意一个地方出现了 noStore,next 在 build 的时候,就等价于路由段配置中的 force-dynamic 或者 fetch 中的 no-store,相当于退出静态渲染,改用动态。

我刚刚去把版本换成 canary without ppr 测试了一下,我用了 noStore 在 API 方法里,然后使用 cache 把 API 包裹起来,生成树还是静态的(注意我没有使用你上面回答里的推荐做法,即把 noStore 放在组件里,放在 cache 方法上方,不知道是不是这里有误解。。)。

```
Route (app) Size First Load JS
┌ ○ / 226 B 98.9 kB
├ ○ /_not-found 871 B 87.8 kB
├ ○ /dashboard 293 B 92.2 kB
├ ○ /dashboard/customers 139 B 87 kB
├ ○ /dashboard/invoices 1.65 kB 95.3 kB
└ ○ /dashboard/invoices/create 172 B 93.8 kB
+ First Load JS shared by all 86.9 kB
├ chunks/23-51b06a4d0afaaa6e.js 31.3 kB
├ chunks/fd9d1056-f593fbcc7b74c7aa.js 53.6 kB
└ other shared chunks (total) 1.91 kB


○ (Static) prerendered as static content
```

而且我测试的时候又遇到个怪问题(为什么我老是遇到怪问题。。。):我在用 canary 版本 build 我做到后面 chapter 的代码的时候,发现 /dashboard/invoices 这个 route 变成 dynamic 的了,然后我慢慢注释一些我觉得可疑的东西,想试试是什么把它变成 dynamic 的。

结果我发现是这行代码。。。(下面有 CONFUSION 的):

```tsx
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string;
page?: string;
};
}) {
// ! CONFUSION: why once use this line, '/dashboard/invoices' became dynamic ??
const query = searchParams?.query || '';
// const currentPage = Number(searchParams?.page) || 1;
// const totalPages = await fetchInvoicesPages(query)
return (
<div className="w-full">
<div className="flex w-full items-center justify-between">
<h1 className={`${lusitana.className} text-2xl`}>Invoices</h1>
</div>
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
<Search placeholder="Search invoices..." />
<CreateInvoice />
</div>
{/* <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
<Table query={query} currentPage={currentPage} />
</Suspense>
<div className="mt-5 flex w-full justify-center">
<Pagination totalPages={totalPages} />
</div> */}
</div>
);
}
```

我真是惊了个呆 o_o ....,这个不是我自己定义的一个 prop 吗。为什么会把 route 变成 dynamic 的。。。

关于上面两点,我 commit 了代码( commit message is: canary test ) push 了,你可以跑跑看。(另外我发现我之前没 push 数据库配置。。。我打开你 fork 的仓库跑起来直接数据出不来。。现在我 push 上去了)

---

> \> Next.js 有一个内置的数据缓存(data cache),可以在传入的服务器请求和部署中保留数据获取的结果。这是可能的,因为 Next.js 扩展了本机 fetch API 以允许服务器上的每个请求设置自己的持久缓存语义。

这部分不懂,动态组件是 PPR 的概念对吧?意思是动态组件也可以调用这个内置的数据缓存吗?如果是这样的话,那 sql 不也是基于 fetch 的吗?意思是通过恰当的配置,可以让一个动态组件每次读的是缓存的数据?

动态组件的情况下,并不意味着我们不能用缓存吗?

> https://github.com/orgs/vercel/discussions/4696
>
> Your response are cached because `@vercel/postgres` uses `fetch` for `sql` and `fetch` is cached by default in Next.js ( https://nextjs.org/docs/app/building-your-application/caching#data-cache). You can use [Segment Config Options]( https://nextjs.org/docs/app/building-your-application/caching#segment-config-options) to control how caching works.
>
> I have created an example at https://github.com/vercel-support/167147-postgres-cache-nextjs/tree/main/app/api where you can compare [the default (cached) response]( https://167147-postgres-cache-nextjs-nsnjr8w00.preview.vercel-support.app/api/default) with [one that's fully dynamic]( https://167147-postgres-cache-nextjs-nsnjr8w00.preview.vercel-support.app/api/no-cache) .

没仔细看它里面贴的网站,但是他这里说 sql fetch 是默认缓存的(因此造成了不修改请求代码的情况下会返回 stale data ),这好像和下面的 revalidate 0 冲突啊,revalidate 0 不是 no-cache 的意思吗?

> 一个悲伤的事情是,正式版的静态渲染和 revalidate 0 配合工作良好,ppr 却不行,这也就意味着,对于带有数据库查询的组件,你并不能 ppr 它们变成完全静态的

我 pull 了你的仓库,试了一下,确实是你说的这样。。

只要调了 sql ,而且没有 noStore 就会报错,除非使用 cache 。

我这里也报关于 validate 0 的问题,我也认为应该是 ppr 和它冲突了。

---

其实我就是想说 callback 的,我自己打错了,我看到你说这句,我回去看我写的那么多个 fallback 我也懵逼了,然后在 Suspense 组件那里看到同名 prop 了,疑似被记忆注入替换了 hoho 。
我整了个 discussion 到 next 的 github community 碰碰运气

https://github.com/vercel/next.js/discussions/63889
@shizhibuyu2023

不过就算是 dynamic 的,也有不复现的概率,我有几次就死活没法复现。有段时间就是会出现这个问题,我不确定是什么原因,总之很奇怪。

喏,又复现了:

https://imgse.com/i/pFTauGt

而且这是在 dynamic 的情况下,意味着每次点这个 home 就调一次数据库:
cost time: 773ms
Fetching revenue data...
cost time: 699ms
Fetching revenue data...
cost time: 213ms
Fetching revenue data...
cost time: 231ms
Fetching revenue data...
Fetching revenue data...
cost time: 216ms
Fetching revenue data...
cost time: 215ms
Fetching revenue data...
Fetching revenue data...
cost time: 246ms
Fetching revenue data...
cost time: 647ms
但是页面刚打开的时候却没有这种问题,很奇怪。
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1320 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 14ms · UTC 17:34 · PVG 01:34 · LAX 10:34 · JFK 13:34
Developed with CodeLauncher
♥ Do have faith in what you're doing.