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

news2024/11/17 1:37:15

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,
            // 再将标识赋值为true
            hasLoadedSSR: true
        }})
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2049654.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

libevent之android与鸿蒙编译过程

背景 最近基于libevent开发了一个端侧的缓存代理库&#xff0c;先是基于macOS编译开发的&#xff0c;基本0问题&#xff0c;后来移植到鸿蒙与android时遇到一些编译链接问题。 libevent版本如下&#xff1a; 软件版本号libevent-2.1.8 android编译 编译环境 android studio…

EmguCV学习笔记

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 笔者的博客网址&#xff1a;https://blog.csdn.net/uruseibest 本教程将分为VB.Net和C#两个版本分别进行发布。 教程VB.net版本请…

Go Roadmap-Basics中文笔记

Go Roadmap-Basics 地址&#xff1a;https://roadmap.sh/golang 简介&#xff1a;Github star No.6 学习路线 Go 中译版 Learn the Basics Go特点&#xff1a;静态类型&#xff0c;运行速度快&#xff0c;编译语言&#xff0c;编译速度快&#xff0c;自动垃圾回收&#xff…

【2】初识JVM

目录 一.什么是JVM 二.JVM的功能 2.1即时编译 三.常见的JVM​编辑 ​编辑 总结​编辑 一.什么是JVM 二.JVM的功能 2.1即时编译 三.常见的JVM 总结

Spring MVC Controller返回json日期格式配置失效的解决办法

如题&#xff0c;Spring MVC 4.3.0版本&#xff0c;配置jackson读写json。Controller层方法返回值对象包含java.util.Date类型的属性&#xff0c;并且在applicationContext.xml中配置了jackson的日期格式&#xff1a; <mvc:annotation-driven><mvc:message-converters…

【ARM+Codesys 客户案例 】RK3568/A40i/STM32+CODESYS在工厂自动化中的应用:PCB板焊接机

现代化生产中&#xff0c;电子元件通常会使用自动化设备来进行生产&#xff0c;例如像PCB&#xff08;印刷电路板&#xff09;的组装。但是生产过程中也会面临一些问题&#xff0c;类似于如何解决在PCB板上牢固、精准地安装各种组件呢&#xff1f;IBL Lttechnik GmbH公司的CM80…

什么是OpenTiny?

OpenTiny 是一套企业级的 Web 前端开发解决方案&#xff0c;提供跨端、跨框架的 UI 组件库和低代码引擎&#xff0c;帮助开发者高效构建 Web 应用 。企业运用开发中&#xff0c;可以利用 OpenTiny 的以下核心组件和优势&#xff1a; TinyVue 组件库&#xff1a;一个丰富的组件库…

AWS boto3 脚本访问 AWS 资源

AWS boto3 脚本访问 AWS 资源 引言boto3主要功能常见用例安装和基本使用 boto3.Client() 低级客户端基本用法关键参数 boto3.resource() 高级客户端常见参数用法 boto3.resource VS boto3.client相似点不同点总结 关于身份验证凭证隐式身份凭证显式身份验证凭证assuem role如何…

对比各类 AWS MySQL 升级方案及原理

搞了好几个月的MySQL升级终于接近尾声&#xff0c;进入总结梳理阶段~ 本文主要对比升级期间用到的三种方案&#xff1a; 本地升级蓝绿升级API同步升级 对比项 \ 升级方式本地升级蓝绿升级API同步升级停机时间长&#xff0c;3-5分钟不可读写较短&#xff0c;约15秒实例变为只…

Delphi 实现JSON序列化和反序列化的功能以及源码探究

目录 一、JSON序列化和反序列化简介 二、Delphi序列化的两种方式 1、TJson的使用 2、TJsonSerializer的使用 3、使用注意事项 三、Delphi与GO序列化效率对比 1、GO语言JSON序列化方法 2、Delphi 与 GO 序列化效率对比 四、Delphi序列化源码初探 五、Delphi 序列化的优…

NGINX 之 location 匹配优先级

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 location 基础语法 1.1 location 语法说明表 1.2 URI部分简单介绍 2 location 匹配优先级 2.1 URI匹配的规则与顺序 2.2 精确匹配(location /1.txt) 2.3 区…

hutool发邮件功能如何配置SMTP服务器参数?

hutool发邮件的教程指南&#xff1f;hutool发邮件性能优化方法&#xff1f; Hutool作为一个轻量级的Java工具库&#xff0c;其邮件发送功能因其简单易用而受到广泛关注。AokSend将详细介绍如何通过配置SMTP服务器参数来实现Hutool发邮件的功能。 hutool发邮件&#xff1a;优势…

LinuxKernel开发

Linux Kernel简介 0. Linux历史 Linux内核&#xff08;英語&#xff1a;Linux kernel&#xff09;是一种开源的类Unix操作系统宏内核。整个Linux操作系统家族基于该内核部署在传统计算机平台&#xff08;如个人计算机和服务器&#xff0c;以Linux发行版的形式[7]&#xff09;…

【Unity实战】NavMeshAgent实现Strafe固定朝向移动

众所周知&#xff0c;NavMeshAgent一旦设定了destination&#xff0c;它就会直奔目标。但是在一些场景中&#xff0c;比如NPC是个射手&#xff0c;除了瞄准玩家&#xff0c;也需要走位。如果不加以处理&#xff0c;我们恐怕会遇见瞄准IK和朝向…难以言表的表现&#xff0c;直接…

达梦数据库的系统视图v$datafile

达梦数据库的系统视图v$datafile 达梦数据库的V$DATAFILE 是一个重要的系统视图&#xff0c;提供了有关数据库数据文件的信息。 V$DATAFILE 系统视图 V$DATAFILE 视图用于显示数据库中每一个数据文件的详细信息。通过查询这个视图&#xff0c;数据库管理员可以了解数据文件的…

从零搭建xxl-job(六):xxl-job执行器服务端的简单搭建

经过前面的学习&#xff0c;相信大家对XXL-JOB调度中心的核心知识点和定时任务的调度流程已经了如指掌了&#xff0c;接下来&#xff0c;我们的重心就要从调度中心过渡到定时任务程序了&#xff0c;接下来&#xff0c;我会为大家搭建起一个简易的可运行的定时任务的体系&#x…

spring揭秘06-Autowired自动绑定依赖及组件自动扫描

文章目录 【README】【1】基于xml配置文件版本的自动绑定【1.1】基于xml配置文件版本的自动绑定代码示例 【2】基于注解版本的自动绑定【2.1】根据类型匹配的注解版自动绑定Autowired【2.2】根据名称匹配的注解版自动绑定AutowiredQualifier【2.2.1】 示例代码 【2.3】关于Prim…

面向财商人群的AI垂直产品 —— AI股票助手

在数字化转型的大潮中,AI技术正在重塑各行各业,尤其是金融市场。对于那些渴望在瞬息万变的股市中保持敏锐洞察力的金融分析师、投资者及股票爱好者来说,一款强大而智能的工具显得尤为重要。今天,我们将向大家介绍一款专为财商人群打造的AI垂直产品——AI股票助手。 一、产…

mac如何恢复被同名替换掉的文件夹 mac文件被替换如何恢复

Mac系统一直以高性能遥遥领先其他的Windows系统&#xff0c;因此&#xff0c;Mac虽然价格远远高出其他的笔记本电脑&#xff0c;但是还是受到了一众用户的青睐。使用mac时&#xff0c;我们也经常会将一个文件命名为已经有了相同文件的文件名&#xff0c;且保存到同一个目标地址…

吴恩达机器学习-C2W3-多类分类

目标 在本实验中&#xff0c;您将探索一个使用神经网络进行多类分类的示例。 工具 您将使用一些绘图例程。它们存储在这个目录下的lab_utils_multiclass_TF.py中。 import numpy as np import matplotlib.pyplot as plt %matplotlib widget from sklearn.datasets import …