type
status
date
slug
summary
tags
category
icon
password
Next.js 给自己的介绍是“The React Framework”,没接触过的同学可能会有疑问?React 已经是一个框架了,为什么还要有 Next.js 呢?其实 Next.js 是为了互补 React 的不足,Next.js 提供了 CSR、SSR、SSG、ISR、 Streaming 这么多渲染方式

CSR(Client Side Rendering)

CSR 也就是客户端渲染,需要使用 JavaScript,调用接口(API)来获取数据,这种方式前后端完全分离。 比如现在有一个博客接口/api/articles,返回 JSON 数据如下
通常 React 项目会使用 create-react-app 来创建项目,我们会在 useEffect 中请求数据。
上面的代码中,页面上还有一个刷新按钮,当数据新增时,接口接口会多返回一条数据,点击刷新按钮,页面上已经存在的 DOM 节点是不更新的,DOM 中只会插入新增的数据,这样我们就会感觉页面渲染很快。 这得益于 React 中引入了虚拟 dom,也就是将真实元素节点抽象成 JavaScript 对象,称之为 VNode,更新 DOM 前会先通过 VDOM 对比,得到要真实更新的 DOM,因此可以有效减少直接操作 dom 次数,从而提高程序性能。 Next.js 团队发布了另一个关于数据请求的 hooks 叫 swr,名字来自于 stale-while-revalidate,意思是过期就会重新验证,它有缓存,聚焦时重新验证,间隔轮询等功能。 与上面代码功能相同,我们可以改成下面代码:

CSR 存在的问题

基于 create-react-app 创建的应用,在 HTML 首次挂载的的时候仅有几个 DOM 节点,类似如下
这就会引起 2 个问题
  • 首次渲染,白屏时间过长;
  • 由于所有 JS 都打包在一个文件中,在这个 JS 加载完成之前,在页面上是看不到任何东西,这就会让用户感受到‘白屏’
  • 对于搜索引擎来说,只能在页面中发现一个 DOM 节点,不利于 SEO;因为搜索引擎是不支持执行 JavaScript 代码的。

SSR(Server Side Rendering)

SSR 也就是服务端渲染,有些同学可能会问“难道要回到 PHP 或者 JSP 时代吗?”,没错 PHP 和 JSP 是服务端渲染,但 Next.js 的 SSR 不同于纯服务端渲染,也拥有着如 SPA 一样快速渲染的能力。传统的服务端渲染只有 HTML 字符串,缺少交互,比如有一个 ClickCounter 组件
经过服务端渲染只能得到最简单的的 HTML。
打印出的 button 点击无效,传统的服务端渲染到此就结束了。而 react 服务端渲染,需要客户端根据服务端生成的页面,继续二次渲染、事件绑定等
服务器端使用 renderToString 直接渲染出的页面信息为静态 html。 客户端根据渲染出的静态 html 进行 hydrate,做一些绑定事件等操作。 因此,若要使用 react 来实现服务端渲染,一般需要 3 个目录,工程配置比较繁琐。 server: 包含 express 的后端工程 client: 包含 react 的前端工程 shared: 包含前后端公用的组件代码。 而在 Nextjs 中,只需要在 Pages 目录下,如下这么写,Next.js 便会自动打包出前后端的代码,拥有 hydrate 的能力
我们需要清楚的是: getServerSideProps 只在服务端执行 Page 组件是在前后端公共执行 所以,在 Page 函数中要注意一些全局对象的使用,比如 window 对象(Node.js 中是不存在的,所以会报错)
我们应该将 window 操作放入 useEffect 中,或者 click 回调函数中,因为这些函数在服务端渲染的时候是自动忽略的。 SSR 解决了白屏问题和 SEO 问题,但是也不是完美的。

SSR 存在的问题

  • 当请求量增大时,每次重新渲染增加了服务器的开销。
  • 需要等页面中所有接口请求完成才可以返回 html,虽不是白屏,但完成 hydrate 之前,页面也是不可操作。

SSG(Static Site Generation)

SSG 也就是静态站点生成,为了减缓服务器压力,我们可以在构建时生成静态页面,备注:Next.js 生成的静态页面与普通的静态页面是不一样的,也是拥有 SPA 的能力,切换页面用户不会感受到整个页面在刷新 比如文章列表页,要生成静态页面,在 Next.js 中代码如下:
使用 getStaticProps 可以获得静态网页的数据,传递给 Page 函数,便可以生成静态页面。博客列表 URL 是固定的,那么不是固定 URL 的页面,要生成静态页面怎么办呢?比如博客详情页。
我们可以使用 getStaticPaths 获得所有文章的路径,返回的 paths 参数会传递给 getStaticProps,在 getStaticProps 中,通过 params 获得文章 id, Next.js 会在构建时,将 paths 遍历生成所有静态页面。 SSG 的优点就是快,部署不需要服务器,任何静态服务空间都可以部署,而缺点也是因为静态,不能动态渲染,每添加一篇博客,就需要重新构建。

ISR(Incremental Static Regeneration)

于是有了一另一种方案 ISR,增量静态生成,在访问时生成静态页面,在 Next.js 中,它比 SSG 方案只需要加了一个参数 revalidate
上面代码表示,当访问页面时,发现 20s 没有更新页面就会重新生成新的页面,但当前访问的还是已经生成的静态页面,也就是:是否重新生成页面,需要根据上一次的生成时间来判断,并且数据会延迟 1 次。 我们可以在页面上显示生成时间
上面代码中我们定义了一个 Time 组件,Time 在客户端渲染,每秒自动刷新。 本地使用运行 yarn build 和 yarn start 来模拟生成环境,测试增量生成。 列表页面可以增量生成,那么详情页呢? 若我们访问不存在的 id,比如 http://localhost:3000/blog/4,页面会显示 404。 getStaticPaths 方法中还有一个参数 fallback 用于控制未生成静态页面的渲染方式。
fallback 有 3 个值 fallback: 'blocking' 未生成的页面使用服务端渲染; fallback: false 未生成的页面访问 404 fallback: true 当访问的静态页面不存在时,会显示 loading,直到静态页面生成返回新的页面。 我们将 fallback 设置为 true,重新访问页面。 revalidate 会额外导致服务器性能开销,20s 生成一次页面是没必要的,比如一些博客网站和新闻网站,文章详情变更没那么频繁。

On-demand Revalidation(按需增量生成)

自从 next v12.2.0 开始支持按需增量生成,我们可以在 page 目录下新建一个 pages/api/revalidate.js 接口,用于触发增量生成。
比如我们在数据库中增加了 2 条数据,此时访问 https://localhost:3000/api/revalidate?secret=<token>&path=/blog/5,便可以触发,生成新的静态页面了。

Server component

Server component 是 React18 提供的能力, 与上面的 SSR 不同,相当于是流式 SSR。 传统 SSR 执行步骤 在服务器上,获取整个应用的数据。 在服务器上,将整个应用程序数据渲染为 HTML 并发送响应。 在浏览器上,加载整个应用程序的 JavaScript 代码。 在客户端,将 JavaScript 逻辑连接到服务端返回的 HTML(这就是“水合”)。 而以上每个步骤必须完成,才可以开始下一个步骤。 比如一个传统的博客页面采用 SSR 的方式使用 getServerSideProps 的方式渲染,那么就需要等 3 个接口全部返回才可以看到页面。
如果评论接口返回较慢,那么整个程序就是待响应状态。 我们可以在 Next.js 13 中开启 app 目录来,使用 Suspense 开启流渲染的能力,将 Comments 组件使用 Suspense 包裹。
组件数据请求使用 use API,就可以实现流渲染了。
如果评论部分接口还在请求中,那么页面左侧注水完成,也是可以交互可以点击的。 因此,Server component 解决了 SSR 中的 3 个问题
> 1. 不必在服务器上返回所有数据才开始返回 html,相反我们可以先返回一个 HTML 结构,相当于骨架屏。 > 2. 不必等待所有 JavaScript 加载完毕才能开始补水。相反,我们可以利用代码拆分与服务器渲染结合使用,React 将在相关代码加载时对其进行水合。 > 3. 不必等待所有组件水合完成,页面才可以交互。

RSC

RSC,英文全称“React Server Component”,中文翻译“服务端组件”。
我们之前讲的 SSR、CSR、SSG、ISR 概念都是页面级别的,页面整体需要是一种渲染类型。但是让我们重新审视一下我们想要渲染的页面。
比如一个博客文章页面,它有纯静态的部分,比如文章内容,也有需要与用户进行交互的部分,比如博客点赞、收藏等功能。
让我们以组件的角度来重新定义这些组件。将纯静态的部分定义为服务端组件。为什么叫服务端组件呢?因为在服务端渲染速度更快又对 SEO 友好,而且渲染出的内容就是 HTML + CSS,这不正好适合纯静态内容吗?
然后把需要与用户交互的部分定义为客户端组件,因为需要用客户端交互,所以一定要用到浏览器 DOM 事件,这就需要在渲染后,在客户端进行水合(添加事件处理程序的过程)。
两种组件的处理截然不同,所以要做区分。于是约定客户端组件添加一个 use client指令表明是客户端组件。
现在一个页面拆分成了多个服务端组件和客户端组件,那么你就很难将这个页面渲染定义为 SSR 或 CSR,所以在 Next.js 13 推出 App Router 后,官方文档也弱化了 SSR、CSR 这些概念,不再提及这些名词。如果你非要往 SSR 或 CSR 这种概念上套,那你可以简单的理解为,服务端组件 SSR,客户端组件 CSR。
现在代码上已经拆分了组件,可是怎么渲染页面呢?
服务端组件很好处理,就在服务端渲染成 HTML + CSS,客户端组件也需要先走一遍 SSR,毕竟客户端组件默认也会返回一点内容,所以也走一遍 SSR,但是客户端组件还要留个记号,表明是客户端组件(有人把这个过程称为“挖洞”)。服务端组件依赖的库和代码不需要打包,留在服务端渲染使用就可。客户端组件依赖的代码需要打包发送到客户端,然后在客户端进行主要的水合和渲染工作。
简单来说就是对所有组件都走一遍 SSR,标记出其中的客户端组件,然后输出 HTML + CSS,同时将客户端组件依赖的代码打包成 JS ,HTML 和 JS 都发送到客户端后,JS 运行,然后对客户端组件进行水合,添加上各种交互事件,由此实现了整个页面的初始渲染工作。
我觉得 RSC 它主要解决了 2 个问题:
第一个是 bundle size,将组件拆分为客户端组件和服务端组件后,服务端组件在服务端渲染即可,客户端只需要最后的渲染结果,所以服务端组件的依赖项不需要打包到客户端 bundle 中,这就减少了客户端 JS 的大小。
第二个是局部渲染和水合,传统的 SSR 实现中,所有的组件代码都要下载到客户端以进行水合,但是在 RSC 中,因为明确进行了组件区分,所以可以做到只有客户端组件进行水合。
而在后续导航的时候,与传统 CSR 在客户端获取数据进行渲染不同,RSC 将组件的渲染放在了服务端,如果直接获取目标路由的 HTML 替换当前的 HTML,将会破坏当前的页面状态,所以采取了一种自定义的格式成为 RSC Payload,它包含服务端组件的渲染结果、客户端组件的占位位置和引用文件、从服务端组件传给客户端组件的数据等信息,然后根据 RSC Payload,客户端可以进行局部渲染和更新,由此实现了状态的保持。
 

总结

  • CSR - 客户端渲染。也就是我们常说的 SPA(single page application),使用 useEffect 获取接口数据,优点是前端后端完全分离,静态部署,缺点是 JavaScript 过大,会造成“白屏”,网页初始 DOM 为空,不利于 SEO,适合开发一些后端管理系统。
  • SSR - 服务器端渲染。优点是解决 SEO 和白屏问题,缺点是每次渲染都会请求服务器,会给服务器造成压力。
  • SSG - 静态站点生成。在构建时获取数据,生成静态页面,只需要静态部署,适合开发一些数据不易变更的网站,比如开发文档。
  • ISR – 增量静态再生。它是 SSG 和 SSR 的组合,主要是靠静态服务,但在数据过期时,可以再次从 API 获取数据,并且生成静态页面,最适合常见的资讯类、新闻类网站。
  • Server component- 也是 SSR 的一种, 但互补了 SSR 的不足,让网页拥有流式渲染的能力。
 

引用

  • https://juejin.cn/post/7407259722430201867
前端开发中常用的js工具函数如何练习TS