欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > NextJs - 服务端/客户端组件之架构多样性设计

NextJs - 服务端/客户端组件之架构多样性设计

2024/10/23 6:27:12 来源:https://blog.csdn.net/Zong_0915/article/details/141278609  浏览:    关键词:NextJs - 服务端/客户端组件之架构多样性设计

NextJs - 服务端/客户端组件之架构多样性设计

  • 前言
  • 一. 架构设计
    • 1.1 SSR+流式渲染常见错误设计之 - 根页面同步阻塞
    • 1.2 架构设计之 - 客户端组件依赖于服务端组件数据
      • ① 使用 Redux 完成数据共享
    • 1.3 架构设计之 - 单页内的分步骤跳转
      • ① 如何做到服务端组件和客户端组件之间的切换
      • ② 进行UI切换的时候如何做到状态保持

前言

本篇文章主要讲解不同场景下,我们怎样去设计客户端和服务端组件的交互,或者是怎么去写代码。本篇文章建立于:使用SSR渲染+Suspense流式渲染,并且服务端/客户端组件混合使用的基础上讲解的。

一. 架构设计

我们知道,NextJsAPP路由模式下,在对应目录下创建一个page.tsx文件,他就会生成对应的路由,我们可以称page.tsx为根页面。

在此基础上,我们说下基本准则:

  1. 根页面(page.tsx)一般作为服务端组件,我们常用于获取一些上下文变量。
  2. 切记不可让根页面作为同步请求获取数据的地方,否则整个页面就会同步阻塞,等待请求返回才能开始渲染。

我们接下来先做个简单的讲解。

1.1 SSR+流式渲染常见错误设计之 - 根页面同步阻塞

在刚开始接触Nextjs这类具备SSR渲染的框架的时候,可能容易写出这样的代码:

  1. 我们在page.tsx根页面中同步阻塞获取接口数据,然后将数据通过Props的形式传递给子组件
  2. 子组件可能是服务端组件、客户端组件。如图:
    在这里插入图片描述

这种写法,从逻辑上它并没有任何问题,但是在Suspense流式渲染的场景下,就没有任何意义。因为阻塞的动作发生在服务端,也就是说:

  1. 必须阻塞所有的异步接口返回,我们的服务器才会开始渲染组件。
  2. 哪怕我们的子组件使用Suspense包装,也没有任何作用。
  3. 我们的页面打开来就会白屏阻塞,阻塞时间取决于这个异步接口的等待返回时间。

正确设计如下:

  1. 我们让异步请求的逻辑,封装在一个粒度尽可能小的服务端组件中,然后使用Suspense包装这个服务端组件。
  2. 这样我们的页面,就不会因为这个请求发生阻塞。就会从上到下,依次渲染相关的组件,而使用Suspense包装的,就会返回对应的fallback效果。
    在这里插入图片描述

倘若在此基础上,我们的客户端组件,需要用到服务端组件中获取的数据,怎么交互?

1.2 架构设计之 - 客户端组件依赖于服务端组件数据

在上述架构图中,我们可以发现,我们的服务端组件是和客户端组件同一层级的。那么同一层级的就无法采用Props的方式传递数据。

那么就可能有读者想:那如果我的客户端组件封装到服务端组件中不就好啦?如图:
在这里插入图片描述
如果这么做:我们的客户端组件就会随着服务端组件同时具备Suspense效果,也就是客户端组件必须等待异步请求返回后才能完成渲染。 但是这样的设计是不合理的,因为我们的客户端组件的渲染不应该等待数据返回再完成渲染。

大家别忘了,我们的客户端组件是可以具备State动态效果的,也就是可以使用useState这样的勾子函数。因此我们可以做到立刻渲染客户端组件,让相关的数据通过State来传递,完成动态渲染。

那么我们如何做到服务端和客户端组件的数据共享呢?

① 使用 Redux 完成数据共享

我们服务端组件,拿到接口数据后,可以将它丢给一个专门的用于存储State的客户端组件,这里我们称之为Context Compoent。它的作用就是:

  • 接收服务端传递的接口数据。
  • 将接口数据保存在Redux中。

在这里插入图片描述

这么做的好处:

  1. 服务端组件的内部渲染,可以直接依赖于接口数据编译为HTML,但是切记服务端组件往往只用来做展示,不具备任何的交互(onChange事件),同时服务端组件一般又通过Suspense封装,可以完成loading效果。
  2. 客户端组件几乎不受服务端组件影响,可以立刻完成渲染,将最基本的UI呈现给用户,而页面相关的数据来自于Redux。当ContextComponent将服务端数据存储到Redux中后,客户端组件自动完成动态渲染。

备注:这样的架构设计一般能满足大多数的开发需求,当然可能有更好的设计,这里只不过提供一种思路。

1.3 架构设计之 - 单页内的分步骤跳转

那么在这个架构设计基础上,倘若我的页面有这样的功能:

  1. 页面加载完毕之后,呈现第一页。
  2. 第一页可以点击:“下一步”,跳转到第二页(同一个URL
  3. 第二页还能够返回到:第一页。同时保持第一页的状态(例如Checkbox的勾选、Input框的内容)

这个功能也就是单页内的分步骤跳转,说白了就是使用同一个URL,但是具有多页效果。下一页的时候,上一页的状态还要保持。只不过UI呈现的是第二页。

但是想要实现单页内的分步骤跳转,有好几个问题需要解决:

  1. 我的首屏UI(第一页)是通过SSR渲染的,怎么做到下一步的时候,把第一页UI切换到第二页的UI?(别忘了,服务端组件是不具备State效果的)
  2. 如何控制Redux的初始化动作只做一次?

① 如何做到服务端组件和客户端组件之间的切换

1.我们在根页面下引入一个RoutePage页面(客户端组件),然后将服务端组件通过Props传递下去:

import ServerComponent from "./ServerComponent";
import RoutePage from "./RoutePage";const Parent = () => {return <><RoutePage slot={<ServerComponent/>}/></>
}export default Parent

RoutePage组件专门用来做UI切换的,也就是控制渲染第一页还是第二页,然后使用Redux来获取全局的状态,我们用一个变量来代表当前是第几页(因为本案例只有两页,就用isServer来表达了)

'use client';
import ClientComponent from "./ClientComponent";
import { ReactNode } from "react";const RoutePage = ({ slot }: { slot: ReactNode }) => {// 假代码const context = useRedux(testState);return <>{/* 如果当前是第一页,就渲染服务端组件,否则渲染客户端组件 */}{context.isServer ? { slot } : <ClientComponent />}</>
}export default RoutePage;

那么isServer的初始值我们设定为true,就做到首屏渲染服务端组件了。我们只要在客户端组件和服务端组件中维护这个State即可完成UI的切换。
设计结构如下:
在这里插入图片描述

备注:

  1. 服务端组件中需要引入额外的一个客户端组件,专门用来控制State。不能在服务端组件中控制State哦。

② 进行UI切换的时候如何做到状态保持

试想一下,第一页首屏加载的时候,数据必定来自于服务端服务端组件里面会引用一个ContextComponent组件,每次渲染的时候都会初始化一遍数据。 假设这里是数据A

倘若第一页有个按钮:加载更多数据。它会发送请求,拉取更多的数据然后呈现在页面上,假设这里获取的数据是:数据B

那么此时第一页呈现的数据是 数据A数据B 的一个并集数据C。那么问题来了:当我们点击下一步,呈现第二页,再次返回第一页的时候,会做什么操作?

  1. 第一页重新触发渲染(但是这里不会触发服务器的SSR渲染),此时服务端组件通过Props传递的初始数据:数据A 还在,会重新赋值给Redux。即导致 数据A 会覆盖 数据C
  2. 那么回到第一页后,之前的数据就被覆盖了,状态也就被刷掉了。

因此我们需要控制,Redux的初始化赋值动作只执行一次。

这个就比较好解决了,我们只需要在Redux中增加一个变量:hasLoadedSSR 一类的标识,代表我们已经SSR渲染过一次了,在Redux赋值的时候加个判断即可,以下是ContextComponent伪代码:

'use client';const ContextComponent = (props)=>{const context = useRedux(testState)const dispatch = useDispatch();const {data} = props;// Redux初始化,如果没有经历过SSR,就完成初始化赋值if(!context.hasLoadedSSR){dispatch({context : {...data,// 再将标识赋值为truehasLoadedSSR: true}})}
}

这样就能防止每次UI切换的时候,初始化状态覆盖当前状态的问题了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com