生活的乐趣取决于生活都本身,而不是取决于工作或地点
大家好,我是柒八九。
前言
在上一篇介绍React 18 如何提升应用性能文章中提到了很多关于React性能优化的方式,例如(Suspence),从底层实现的角度来看,都是基于React Server Component(简称RSC)来做文章.
在2020年末,React团队引入了Zero-Bundle-Size React Server Components概念。自那以后,React开发者社区一直在尝试并学习如何应用这种前瞻性的方法。
React改变了我们构建用户界面的思维方式。而使用RSC的新模型更加结构化、方便、可维护,并提供了更好的用户体验。
最新版本的Next.js 13已经采用了以服务器组件思维方式,并将其作为默认选项。作为React开发者,我们必须适应这种新的思维模式,以充分发挥其在构建应用程序方面的优势。
RSC很好的将服务器端渲染与客户端JavaScript的交互性无缝地融合在一起。
所以,我们有必要用一篇文章来介绍它.(文章有点长,请耐心观看,并且内容有点内核,配合收藏观看更佳)
你能所学到的知识点
- 前置知识点
- React :客户端UI库
- React 应用的通病
- SSR 和 Suspense 解决的痛点
- 客户端-服务器模型
- RSC的红与黑
- 如何同时使用客户端组件和服务器组件
- RSC的优点
好了,天不早了,干点正事哇。
1. 前置知识点
网络瀑布效应
网络瀑布效应(Network Waterfall Effect)是一个用于描述在计算机网络中出现的性能问题的概念。它通常用来说明在复杂的网络环境中,一个小问题的出现可能会逐渐扩大,导致整个网络的性能下降。(类似多米诺骨牌一样)
网络瀑布效应的核心思想是,网络中的各个组件和节点之间相互依赖,一个组件的问题可能会影响到其他组件,从而引发连锁反应。
这种效应通常在大规模、分布式的网络中更为显著,因为网络中的节点众多,问题的传播速度和范围都会加大。
这意味着后续的获取请求仅在前一个获取请求被解析或完成后才会被初始化。
水合(Hydration)
在计算机科学领域,水合(Hydration)通常指的是将数据或状态从一种格式或状态转换为另一种格式或状态的过程。
React/Vue 水合
React和Vue的水合流程大差不差(反正都是各自SSR流程中的一部分,只是具体API不同,原理都是一样的),所以我们只按其中一种介绍,另外一种或者说其他更多的前端框架,你只需要换个名字就可以了. (按图索骥,照猫画虎会哇).
"React 水合"(
React Hydration)是指将服务器端渲染(Server-Side Rendering,SSR)得到的HTML 结构与客户端的JavaScript 表现层相结合的过程。
React 水合是在客户端渲染(Client-Side Rendering,CSR)和服务器端渲染之间的一个关键步骤,确保在将服务器渲染的 HTML 呈现给用户之后,React 组件能够在客户端接管并继续工作。
以下是 React 水合的详细步骤和背后的原理:
- 服务器端渲染(SSR):在
服务器端使用React渲染组件,生成一段包含完整组件结构的HTML。
- 这段
HTML可以包含组件的初始状态,这样在首次加载页面时,用户将看到已经有内容呈现在页面上,而不需要等待客户端JavaScript加载和执行。
- 客户端 JavaScript 加载:在浏览器中加载包含
React应用逻辑的JavaScript文件。
- 这些文件可能包括应用的组件、状态管理逻辑、事件处理等。
- 水合阶段:一旦客户端
JavaScript加载完成,React 将接管页面,开始水合过程。
- 这意味着
React会检查服务器端渲染生成的HTML,并将其与客户端JavaScript中的组件逻辑进行匹配。
- 组件恢复和事件绑定:在水合阶段,
React会将服务器端渲染的HTML中的组件恢复到其初始状态,并建立与客户端JavaScript中的相应组件的联系。
- 这包括建立事件绑定、状态同步等。
- 交互和动态更新:一旦水合完成,
React组件就会变得可交互。
- 用户可以与页面进行互动,而客户端
JavaScript负责处理事件、状态更改等 - 此后,页面将继续响应用户操作,动态地更新内容,而无需再次从服务器获取完整的
HTML。
React 水合的优势在于它结合了服务器端渲染和客户端渲染的优点,提供了更好的性能和用户体验。
通过在首次加载时提供一部分已渲染的内容,用户可以更快地看到页面,并与之互动。然后,客户端
JavaScript接管页面,继续处理后续的交互和动态更新。
Next 简单使用教程
Next.js 是一个基于 React 的框架,用于构建具有服务器端渲染(SSR)和静态网站生成(SSG)功能的应用程序。Next.js 提供了一个内置的路由系统,称为 Next.js App Router,用于管理应用程序的路由和页面导航。
下面是一个简单的介绍和代码示例,展示如何使用 Next.js App Router:
- 安装 Next.js: 首先,你需要在项目中安装
Next.js。你可以使用npm或yarn进行安装。
npm install next react react-dom # 或 yarn add next react react-dom
- 创建页面: 在
Next.js中,页面是位于pages目录下的React组件。每个页面对应一个 URL 路由。
在项目根目录下创建pages目录,并在其中创建一个名为index.js的文件,作为默认页面:
// pages/index.js function HomePage() { return ( <div> <h1>前端柒八九</h1> </div> ); } export default HomePage;
- 导航: Next.js App Router 提供了
Link组件来实现内部页面之间的导航。
// pages/index.js import Link from 'next/link'; function HomePage() { return ( <div> <h1>前端柒八九</h1> <Link href="/about"> <a>北宸南蓁</a> </Link> </div> ); } export default HomePage;
// pages/about.js function AboutPage() { return ( <div> <h1>北宸南蓁</h1> </div> ); } export default AboutPage;
- 启动开发服务器: 在终端中运行以下命令以启动
Next.js开发服务器。
npm run dev # 或 yarn dev
- 访问
http://localhost:3000可以看到主页,点击 "北宸南蓁" 链接可以切换到关于页面。
Next.js App Router 简化了页面导航和路由管理,使开发者能够更轻松地创建多页面应用。使用 Link 组件可以实现内部页面之间的无刷新切换,而无需重新加载整个页面。这对于提供更好的用户体验非常有帮助。
2. React :客户端UI库
自诞生以来,React 一直是一个客户端UI库。它是一个基于JavaScript的开源库,帮助Web和移动开发者构建采用组件化架构的应用程序。
React的哲学建议我们将整个设计拆分成更小、自包含的组件,称为组件(components)。
然后,这些组件可以拥有自己的私有数据,称为状态(state),以及在其他组件之间传递数据的方式,称为属性(props)。我们将这些组件分解为一个组件层次结构,定义状态,管理改变状态的效果,并决定数据的流动。
所有
React组件都是JavaScript函数。
当应用程序在浏览器上加载时,我们下载组件代码并使用它们使应用程序正常运行。
3. 传统 React 应用的通病
React客户端组件在解决特定用例方面表现良好。但是,在有些场景下,它表现的差强人意.
现在让我们看一下我们可能会遇到的一些常见问题示例。
布局抖动
一个非常常见的用户体验问题是组件渲染时突然的布局变化。
const App = () => { return ( <Wrapper> <ComponentA /> <ComponentB /> </Wrapper> ) } 我们有两个组件
我们有两个组件,ComponentA 和 ComponentB,它们作为子组件传递给一个 Wrapper 组件。
每个组件的主体看起来类似于这样:
Wrapper 组件
const Wrapper = ({children}) => { const [wrapperData, setWrapperData] = useState({}); useEffect(() => { // 模拟异步接口 getWrapperData().then(res => { setWrapperData(res.data); }); }, []); // 只有异步接口,成功返回,才开始渲染子组件(通过children) return ( <> <h1>{wrapperData.name}</h1> <> {wrapperData.name && children} </> </> ) }
ComponentA组件
const ComponentA = () => { const [componentAData, setComponentAData] = useState({}); useEffect(() => { getComponentAData().then(res => { setComponentAData(res.data); }); }, []); return ( <> <h1>{componentAData.name}</h1> </> ) }
ComponentB组件
const ComponentB = () => { const [componentBData, setComponentBData] = useState({}); useEffect(() => { getComponentBData().then(res => { setComponentBData(res.data); }); }, []); return ( <> <h1>{componentBData.name}</h1> </> ) }
每个组件都负责获取自己的数据。因此,每个组件都不受其他组件数据的干扰。这种情况,貌似很玩美. (徐志胜语音包)
但是,如果遇到下面的情况,阁下该如何应对呢. 让我们慢慢道来.
假设从每个组件发起的 API 调用获取响应的时间如下:
<Wrapper />获取响应需要1 秒<ComponentB />获取响应需要2 秒<ComponentA />获取响应需要3 秒
这里就会出现很匪夷所思的场景
Wrapper在 1 秒后对用户可见。- 然后
ComponentB在 2 秒后出现。 - 经过 3 秒,
ComponentA出现。但是ComponentA的出现会将ComponentB推下去,就好像ComponentA突然冒出来一样。这不是很好的用户体验。
从网上找了一个类似的效果,大家可以不必要特意强调组件名称的异同.只看对应的效果即可.
这就是我们常说的页面抖动,而这个情况,又会产生布局位移。 导致网页视觉稳定性很差. 如果想了解更过,可以查看我们之前写的CLS.
当然,我们可以通过使用加载指示器或闪烁效果来改善体验,告诉用户稍后会有一些内容出现。但是,这个效果(自认为)是一种掩耳盗铃的方式.
网络瀑布流
另一个问题是,子组件(ComponentA 和 ComponentB)甚至在 Wrapper 组件从其所发起的 API 调用获取响应之前都没有被渲染出来,这导致了一个瀑布效应(Waterfall)。连续的数据获取总是会引入瀑布效应。
在我们的示例中,只有在 Wrapper 组件中的 API 调用获取响应之后,其他两个组件才会被渲染出来。
可维护性问题
我们如何解决瀑布问题呢?
现在假设我们的任何组件都不进行任何网络调用。我们一次性使用单个API调用fetchAllDetails()获取所有组件的详细信息,包括父组件在内。
之后,我们将所需的信息作为props传递给每个组件。这种处理方式,或多或少可以减少瀑布问题
const App = () => { // 假设,该网络调用在合适的地方进行调用(`useEffect`等) const info = fetchAllDetails(); return( <Wrapper ino={info.wrapperInfo} > <ComponentA ino={info.AInfo} /> <ComponentB ino={info.BInfo} /> </Wrapper> ) }
这种方法并没有什么问题。但是,API 的响应与我们的组件之间耦合度很高。这可能会导致一些可维护性问题。
假设有一天,善变小可爱产品决定放弃ComponentA功能。那么我们可以简单地去掉上面代码中的ComponentA组件。这没问题!我们还希望从 API 响应中删除 AInfo,因为我们不想处理组件未使用的数据。毕竟,如果没有 ComponentA,那就不需要 AInfo。



