Nextjs15 实战 - React Notes之SidebarNoteList优化和Suspense的使用
current branch 对应如下文档
redis
ioredis
本专栏内容均可在Github:notes_02 找到
完整项目使用技术栈:
Nextjs15 + MySQL + Redis + Auth + Prisma + i18n + strapi + Docker + vercel
一、本节目标
实现笔记列表展开回收和 Suspense 的实践
二、修改根布局和其他页面
修改 app/components/SidebarNoteList.tsx:
import SidebarNoteItem from "@/components/SidebarNoteItem";export default async function NoteList({ notes }) {const arr = Object.entries(notes);if (arr.length == 0) {return <div className="notes-empty">{"No notes created yet!"}</div>;}return (<ul className="notes-list">{arr.map(([noteId, note]) => {return (<li key={noteId}><SidebarNoteItem noteId={noteId} note={JSON.parse(note)} /></li>);})}</ul>);
}
这里我们将具体的每条笔记抽离成单独的 SidebarNoteItem
组件,
app/components/SidebarNoteItem.tsx 代码如下:
import dayjs from "dayjs";
import SidebarNoteItemContent from "@/components/SidebarNoteItemContent";export default function SidebarNoteItem({ noteId, note }) {const { title, content = "", updateTime } = note;return (<SidebarNoteItemContentid={noteId}title={note.title}expandedChildren={<p className="sidebar-note-excerpt">{content.substring(0, 20) || <i>(No content)</i>}</p>}><header className="sidebar-note-header"><strong>{title}</strong><small>{dayjs(updateTime).format("YYYY-MM-DD hh:mm:ss")}</small></header></SidebarNoteItemContent>);
}
这里我们又抽离了一个 SidebarNoteItemContent
组件,用来实现展开和收回功能,我们将笔记的标题和时间的 JSX
作为 children
传递给了 SidebarNoteItemContent
app/components/SidebarNoteItemContent.tsx 代码如下:
"use client";import { useState, useRef, useEffect, useTransition } from "react";
import { useRouter, usePathname } from "next/navigation";export default function SidebarNoteContent({id,title,children,expandedChildren,
}) {const router = useRouter();const pathname = usePathname();const selectedId = pathname?.split("/")[1] || null;const [isPending] = useTransition();const [isExpanded, setIsExpanded] = useState(false);const isActive = id === selectedId;// Animate after title is edited.const itemRef = useRef(null);const prevTitleRef = useRef(title);useEffect(() => {if (title !== prevTitleRef.current) {prevTitleRef.current = title;itemRef.current.classList.add("flash");}}, [title]);return (<divref={itemRef}onAnimationEnd={() => {itemRef.current.classList.remove("flash");}}className={["sidebar-note-list-item",isExpanded ? "note-expanded" : "",].join(" ")}>{children}<buttonclassName="sidebar-note-open"style={{backgroundColor: isPending? "var(--gray-80)": isActive? "var(--tertiary-blue)": "",border: isActive? "1px solid var(--primary-border)": "1px solid transparent",}}onClick={() => {const sidebarToggle = document.getElementById("sidebar-toggle");if (sidebarToggle) {sidebarToggle.checked = true;}router.push(`/note/${id}`);}}>Open note for preview</button><buttonclassName="sidebar-note-toggle-expand"onClick={(e) => {e.stopPropagation();setIsExpanded(!isExpanded);}}>{isExpanded ? (<imgsrc="/chevron-down.svg"width="10px"height="10px"alt="Collapse"/>) : (<img src="/chevron-up.svg" width="10px" height="10px" alt="Expand" />)}</button>{isExpanded && expandedChildren}</div>);
}
这里 SidebarNoteItemContent
具体的实现其实并不重要,你只要知道这是一个客户端组件就行了。在这个客户端组件里我们用了 useState
来控制展开和收回的状态,然后添加了一些动画效果,仅此而已。如果步骤正确的话,此时的页面效果为:
这个时候你可能会有个疑问:为什么要这样做呢?为什么不直接把 SidebarNoteItem
声明为客户端组件,然后直接在这个组件里全部实现呢?还要用传递 children
这么复杂的方式?
考验你是否认真学习了之前的知识到了!
在这段代码中,SidebarNoteItem
是一个服务端组件,在这个组件中我们引入了 dayjs 这个库,然而我们却是在 SidebarNoteItemContent
这个客户端组件中使用的 dayjs
。请问最终客户端的 bundle
中是否会打包 dayjs
这个库?
答案是不会。在服务端组件中使用 JSX
作为传递给客户端组件的 prop
,JSX
会先进行服务端组件渲染,再发送到客户端组件中。也就是说,发送给客户端组件的并不是:
<header className="sidebar-note-header"><strong>{title}</strong><small>{dayjs(updateTime).format("YYYY-MM-DD hh:mm:ss")}</small>
</header>
而是编译后的如:
<header class="sidebar-note-header"><strong>ea molestias</strong><small>2023-12-13 05:19:48</small>
</header>
三、使用 Suspense 实现骨架图的效果
因为我们现在将笔记列表数据的获取放在了顶层,所以直接为 SidebarNoteList
添加 Suspense
是没有效果的,我们需要将数据获取改为在 SidebarNoteList
组件内部。
修改 app/components/Sidebar.tsx 代码如下:
import React, { Suspense } from "react";
import Link from "next/link";
import SidebarNoteList from "../components/SidebarNoteList";
import EditButton from "./EditButton";
import NoteListSkeleton from "./NoteListSkeleton";export default async function Sidebar() {return (<><section className="col sidebar"><Link href={"/"} className="link--unstyled"><section className="sidebar-header"><imgclassName="logo"src="/favicon.ico"width="22px"height="20px"alt=""role="presentation"/><strong>React Notes</strong></section></Link><section className="sidebar-menu" role="menubar"><EditButton noteId={null}>New</EditButton></section><nav><Suspense fallback={<NoteListSkeleton />}><SidebarNoteList /></Suspense></nav></section></>);
}
添加 app/components/NoteListSkeleton.tsx,代码如下:
export default function NoteListSkeleton() {return (<div><ul className="notes-list skeleton-container"><li className="v-stack"><divclassName="sidebar-note-list-item skeleton"style={{ height: "5em" }}/></li><li className="v-stack"><divclassName="sidebar-note-list-item skeleton"style={{ height: "5em" }}/></li><li className="v-stack"><divclassName="sidebar-note-list-item skeleton"style={{ height: "5em" }}/></li></ul></div>);
}
app/components/SidebarNoteList.tsx 代码如下,为了让效果更加明显,我们添加了一个 sleep
函数:
import SidebarNoteItem from "../components/SidebarNoteItem";
import { getAllNotes } from "@/lib/redis";export default async function NoteList() {const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));await sleep(1000);const notes = await getAllNotes();const arr = Object.entries(notes);if (arr.length == 0) {return <div className="notes-empty">{"No notes created yet!"}</div>;}return (<ul className="notes-list">{arr.map(([noteId, note]) => {return (<li key={noteId}><SidebarNoteItem noteId={noteId} note={JSON.parse(note)} /></li>);})}</ul>);
}
此时页面效果如下:
那么问题来了,SidebarNoteList
用 Suspense
和不用 Suspense
,具体有什么改变呢?
我们把 sleep
的时间设置为 3s
。这是不使用 Suspense
的效果,我们从 github
主页输入地址 http://localhost:3000/:
输入地址后,我们等待了大概 3s
后,页面突然完全展现。
这是使用 Suspense 的效果,我们还是从github
主页输入地址 http://localhost:3000/:
我们可以看到,使用 Suspense
,数据加载不会阻塞页面,也就是说在笔记列表还在加载的时候,用户依然可以与页面其他部分进行交互,比如点击 New
按钮新建笔记。
相关文章:
Nextjs15 实战 - React Notes之SidebarNoteList优化和Suspense的使用
current branch 对应如下文档 redis ioredis 本专栏内容均可在Github:notes_02 找到 完整项目使用技术栈: Nextjs15 MySQL Redis Auth Prisma i18n strapi Docker vercel 一、本节目标 实现笔记列表展开回收和 Suspense 的实践 二、修改根…...
第三十章:Python-NetworkX库:创建、操作与研究复杂网络
一、NetworkX库简介 NetworkX是一个强大的Python库,用于创建、操作和研究复杂网络(图)的结构、动态和功能。它支持多种类型的图,包括无向图、有向图、加权图和多重图,并提供了丰富的图论算法和可视化工具。资源绑定附…...
cpp自学 day19(多态)
一、基本概念 同一操作作用于不同的对象,产生不同的执行结果 👉 就像「按F1键」:在Word弹出帮助文档,在PS弹出画笔设置,同一个按键触发不同功能 (1)多态类型 类型实现方式绑定时机静态多态…...
Unity:销毁(Destroy)
Destroy的基本概念 Destroy是Unity提供的一个方法,用于立即或延迟销毁游戏对象(GameObject)或其组件(Component)。它会从场景中移除对象,并释放相关资源(比如内存)。 语法 销毁Ga…...
【C++初阶】模板进阶
目录 模板参数 模板的特化 函数特化 类模板特化 全特化 偏特化 模板分离编译 分离编译 模板的分离编译 为什么模板不支持声明和定义分离呢? 解决方法 模板总结 优点 缺点 模板参数 模板参数分为类型形参和非类型参数 类型形参:出现在模板…...
BN 层的作用, 为什么有这个作用?
BN 层(Batch Normalization)——这是深度神经网络中非常重要的一环,它大大改善了网络的训练速度、稳定性和收敛效果。 🧠 一句话理解 BN 层的作用: Batch Normalization(批归一化)通过标准化每一…...
CNN 里面能自然起到防止过拟合的办法
在 CNN(卷积神经网络)中,其实有 一些结构和机制 天然就具有防止过拟合(overfitting)的作用,不完全依赖额外的正则化手段。 🧠 一、CNN 天然防过拟合的几个原因: 1️⃣ 局部连接&…...
存储基石:深度解读Linux磁盘管理机制与文件系统实战
Linux系列 文章目录 Linux系列前言一、磁盘1.1 初识磁盘1.2 磁盘的物理结构1.3 磁盘的存储结构1.4 磁盘的逻辑结构 二、文件系统2.1 系统对磁盘的管理2.2 文件在磁盘中的操作 前言 Linux 文件系统是操作系统中用于管理和组织存储设备(如硬盘、SSD、USB 等ÿ…...
AI Agent设计模式六:ReAct
概念 :思考-执行循环系统 ✅ 优点:提升任务完成度,适合复杂问题拆解❌ 缺点:执行延迟较高,资源消耗大 from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage, AIMessage from langgraph.pr…...
使用MySQL时出现 Ignoring query to other database 错误
Ignoring query to other database 错误 当在远程连接软件中输入MySQL命令出现该错误 导致错误原因是:登录mysql时账户名没有加上u 如果出现该错误,退出mysql,重新输入正确格式进入即可!...
(三)链式工作流构建——打造智能对话的强大引擎
上一篇:(二)输入输出处理——打造智能对话的灵魂 在前两个阶段,我们已经搭建了一个基础的智能对话,并深入探讨了输入输出处理的细节。今天,我们将进入智能对话的高级阶段——链式工作流构建。这一阶段的目…...
跳跃连接(Skip Connection)与残差连接(Residual Connection)
1. 跳跃连接(Skip Connection)的基本概念 跳跃连接是一种在深度神经网络中广泛应用的技术,它允许信息在网络中跨层直接传递。在传统的神经网络里,每一层的输出仅仅是前一层输出经过特定变换后的结果。而在具备跳跃连接的网络中&a…...
[特殊字符] 通过Postman和OAuth 2.0连接Dynamics 365 Online的详细步骤 [特殊字符]
🌟 引言 在企业应用开发中,Dynamics 365 Online作为微软的核心CRM平台,提供了强大的Web API接口。本文将教你如何通过Postman和OAuth 2.0认证实现与Dynamics 365的安全连接,轻松调用数据接口。 📝 准备工作 工具安装…...
什么是RPC通信
RPC(Remote Procedure Call,远程过程调用)通信是一种允许程序像调用本地函数一样调用远程服务器上函数的通信技术。它简化了分布式系统中的网络交互,隐藏了底层网络通信的复杂性,使开发者能够专注于业务逻辑。 一、RPC…...
HANA如何在存储过程里执行动态SQL
业务场景需求: 在HANA里如何实现动态的SQL控制,比如需要多个单据里,实现某个自定义字段不允许重复 一般的写法是需要在每个业务单据里加对应的存储过程控制,这样的话,需要在每个业务单据里进行控制,SQL维…...
NO.66十六届蓝桥杯备战|基础算法-贪心-区间问题|凌乱的yyy|Rader Installation|Sunscreen|牛栏预定(C++)
区间问题是另⼀种⽐较经典的贪⼼问题。题⽬⾯对的对象是⼀个⼀个的区间,让我们在每个区间上做出取舍。 这种题⽬的解决⽅式⼀般就是按照区间的左端点或者是右端点排序,然后在排序之后的区间上,根据题⽬要求,制定出相应的贪⼼策略&…...
0101安装matplotlib_numpy_pandas-报错-python
文章目录 1 前言2 报错报错1:ModuleNotFoundError: No module named distutils报错2:ERROR:root:code for hash blake2b was not found.报错3:**ModuleNotFoundError: No module named _tkinter**报错4:UserWarning: Glyph 39044 …...
SQL ServerAlways On 可用性组配置失败
问题现象: 配置 Always On 可用性组时,报错 “无法将数据库加入可用性组”(错误 41158),或提示 “WSFC 群集资源无法联机”(错误 19471)。 快速诊断 验证 WSFC 群集状态: # 检查群集…...
01 - UnLua访问蓝图
前文回顾:配置好了智能提示和调试 分别对私有的和公开函数,变量,组件,事件进行测试。 测试 在BeginPlay中,分别访问他们。这里引入了GetDisplayName函数打印相机组件名 打印结果: 结论 不管是私有的&…...
6.5.图的基本操作
一.图的基本操作: 1.判断图G是否存在弧<x,y>或边(x,y): a.使用邻接矩阵来实现判断无向图G中是否存在边(x,y): 以上述图片的无向图为例,用邻接矩阵存储无向图时想要判断两个顶点之间是否有边是很方便的, 比如判…...
2025全新开源双端系统源码:获取通讯录、相册、短信、定位及已装应用信息
分享一套全新上线的双端信息采集系统源码,支持提取通讯录、相册、短信、定位信息及已安装应用数据。源码完全开源,只做轻微测试需要的自取,简易教程放在压缩包里面了,欢迎有需要的朋友自取参考。 下载地址:下载地址.t…...
es基本概念
Elasticsearch 的架构与基本概念 Elasticsearch(简称 ES)是一个开源的分布式搜索和分析引擎,基于 Apache Lucene 构建。它被广泛用于全文搜索、日志分析、实时数据分析等场景。以下是其架构概述及其基本概念的详细解释。 Elasticsearch 的架…...
算法刷题记录——LeetCode篇(2.5) [第141~150题](持续更新)
更新时间:2025-04-04 算法题解目录汇总:算法刷题记录——题解目录汇总技术博客总目录:计算机技术系列博客——目录页 141. 环形链表 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通…...
【Rust学习】Rust数据类型,函数,条件语句,循环
本文专栏:Rust学习 目录 一,数据类型 1,标量类型 1.1,整型 1.2,整型溢出 1.3,浮点数型 1.4,布尔类型 1.5,字符型 2,复合类型 2.1,Tuple(元组) 2.2&am…...
PgVectore的使用
PgVectore的使用 一、PgVector的安装 参照博客:https://blog.csdn.net/u012953777/article/details/147013691?spm1001.2014.3001.5501 二、PgVector的使用 1、创建表与插入数据 定义向量字段: CREATE TABLE items (id SERIAL PRIMARY …...
智能工厂的数字孪生与信息物理系统架构研究
摘要 本文以工业 4.0 为背景,系统分析数字孪生(Digital Twin)与信息物理系统(CPS)在智能工厂中的协同架构。通过构建 "感知 - 映射 - 决策 - 执行" 的四层技术框架,结合三一重工、海尔等企业案例…...
基于YOLO11实例分割与奥比中光相机的快递包裹抓取点检测
本博客来源于CSDN机器鱼,未同意任何人转载。 更多内容,欢迎点击本专栏,查看更多内容。 0 引言 项目采用六轴机械臂搭配末端真空吸盘,从无序包裹中抓取想要的包裹。AI算法需要提供各包裹的抓取点的3D坐标与3D姿态。由于快递包裹含…...
简单程序语言理论与编译技术·22 实现一个从AST到RISCV的编译器
本文是记录专业课“程序语言理论与编译技术”的部分笔记。 LECTURE 22(实现一个从AST到RISCV的编译器) 一、问题分析 1、完整的编译器(如LLVM)需先完成AST到IR的转换,并进行代码优化,再到汇编࿰…...
无锡无人机驾驶证培训费用
无锡无人机驾驶证培训费用,随着科技的迅速发展,无人机在众多行业中发挥着举足轻重的作用。从影视制作到农业监测,再到物流运输与城市规划,无人机的应用场景不断扩展,因此越来越多的人开始意识到学习无人机驾驶技能的重…...
[ctfshow web入门] web5
前置知识 引用博客:phps的利用 当服务器配置了 .phps 文件类型时,访问 .phps 文件会以语法高亮的形式直接显示 PHP 源代码,而不是执行它。.phps被作为辅助开发者的一种功能,开发者可以通过网站上访问xxx.phps直接获取高亮源代码 …...
第五章:架构安全性_《凤凰架构:构建可靠的大型分布式系统》
第五章 架构安全性 一、认证机制 核心知识点: 认证标准: HTTP Basic认证:Base64编码传输凭证,需配合HTTPS使用OAuth 2.0:授权框架,重点掌握四种授权模式: 授权码模式(最安全&#…...
控件主题效果添加程序设计
以下是针对Qt Designer设计的控件添加阴影效果的完整解决方案,结合可视化设置与动态主题支持: 一、基础阴影效果实现方案 1. 通过QSS实现简易阴影(适用于简单需求) /* 使用多重边框模拟阴影效果 */ QFrame#customWidget {borde…...
用swift playground写个ios应用和大模型或者网站交互
import SwiftUIstruct ContentView: View {State private var textFieldText: String ""State private var outputText: String "输出将会显示在这里"private let tip:String "消息已发送,请等待"State private var history:[Stri…...
Mlivus Cloud SDK v2的革新:从痛点剖析到实战优化
目录 从V1到V2:开发者体验的范式转变 深度解析SDK v2的架构革新 1. 统一接口范式:终结API混乱时代 2. 原生异步支持:高并发场景的性能救星 3. Schema Cache机制:性能优化的隐形冠军 4. 全功能REST API:简化集成的关键 实战指南:从迁移到深度优化 平滑迁移策略 性…...
【图像处理基石】什么是AWB?
1. AWB(自动白平衡)的定义 AWB(Auto White Balance)是一种图像处理技术,通过算法校正不同色温光源下图像的色彩偏差,使白色在任何光照条件下都能准确呈现为白色,从而让图像颜色更接近人眼真实感…...
[蓝桥杯 2017 省 B] k 倍区间
P8649 [蓝桥杯 2017 省 B] k 倍区间 题目描述 给定一个长度为 N N N 的数列, A 1 , A 2 , ⋯ A N A_1,A_2, \cdots A_N A1,A2,⋯AN,如果其中一段连续的子序列 A i , A i 1 , ⋯ A j ( i ≤ j ) A_i,A_{i1}, \cdots A_j(i \le j) Ai,Ai1,⋯…...
基于SSM的高校宿舍水电管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
【LLM系列】1.大模型简介
1. 基础 1.1 如何权衡模型的复杂度和性能? ├── a. 模型架构选择 │ ├── 简化架构 │ │ └── 选择较小的网络层数和宽度,降低复杂度; │ │ 可使用高性能基础模型如 Transformers 作为起点,根据需求缩放模型。 │ └──…...
从概念和设计哲学的角度详细解析工厂模式
从概念和设计哲学的角度详细解析工厂模式。 1. 工厂模式的核心目标 解耦:将对象的创建过程与使用过程分离,使用者无需关心对象如何被创建。 统一入口:通过一个接口或方法控制对象的生成,隐藏底层实现细节。 类比现实中的工厂&am…...
AI小白:机器学习VS深度学习
1 特征工程的范式革命 传统机器学习:手工特征工程的艺术 在传统机器学习中,特征工程是一个关键步骤,它涉及将原始数据转化为能够被机器学习模型高效利用的特征。这通常需要领域专家的经验和知识,以手动设计和提取特征。 例如&a…...
对应列表数据的分割和分组
要基于指定的流派列表分割数据,可以使用 布尔索引 或 groupby 结合筛选。以下是具体方法: 场景假设 数据列 genres 中的值可能是多流派的字符串,例如 "drama,action" 或 ["drama", "action"]。目标࿱…...
信息物理系统(CPS):中国 AI(DEEPSEEK)的未来路径
一、引言 人工智能(AI)的发展正从通用模型向垂直领域渗透,而信息物理系统(CPS)作为连接数字世界与物理世界的桥梁,为 AI 提供了新的发展方向。中国 AI 企业如 DEEPSEEK 通过开源策略和本土化优势ÿ…...
SEO长尾词优化实战技巧
内容概要 长尾关键词作为SEO策略的重要组成部分,能够有效捕捉细分领域的精准流量,降低竞争成本的同时提升转化效率。本文系统梳理了从关键词挖掘到流量转化的全链路优化方法,重点解析工具使用、布局策略及搜索意图匹配三大核心模块。通过结合…...
爬虫自动化工具:DrissionPage
1. DrissionPage初始 官网地址:🛰️ 概述 | DrissionPage官网 在当今互联网高速发展的时代,网页数据的获取和处理变得愈发重要。传统的自动化工具如 Selenium 在某些方面逐渐显露出一些局限性,而 DrissionPage 正是在这样的背景下…...
扩展库Scrapy:Python网络爬虫的利器
目录 一、扩展机制的核心原理 二、六大实用扩展库详解 1. 动态渲染神器:scrapy-playwright 2. 分布式架构:scrapy-redis 3. 反反爬利器:scrapy-zyte-smartproxy 4. 智能调度:scrapy-thunder 5. 数据管道:scrapy…...
L3-21
exer01 Message # 1.定义Message消息类和cmd,content,sender,to四个属性,其中to默认为None class Message:def __init__(self, cmd, content, sender, toNone):self.cmd cmdself.content contentself.sender senderself.to to # 2. 创建登录消息对象msg1,聊天消…...
04.游戏开发-unity编辑器详细-工具栏、菜单栏、工作识图详解
04.游戏开发,unity编辑器详细-工具栏、菜单栏、工作识图详解 提示:帮帮志会陆续更新非常多的IT技术知识,希望分享的内容对您有用。本章分享的是Python基础语法。前后每一小节的内容是存在的有:学习and理解的关联性,希…...
GRBL运动控制算法(二)圆弧插补
前言 GRBL 是一款高性能、开源的嵌入式 CNC(计算机数控)控制器固件,专为 Arduino 平台优化,广泛应用于雕刻机、激光切割机、3D 打印机及其他精密运动控制场景。自 2009 年发布以来,GRBL 凭借其高效的运动规划算法、稳…...
《P1072 [NOIP 2009 提高组] Hankson 的趣味题》
题目描述 Hanks 博士是 BT(Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson。现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题。 今天在课堂上,老师讲解了如何求两个正整数 c1 和 c2 的最大公约数…...
矩阵分解中的梯度下降:详细实现方案(包含数学推导、代码实现和优化技巧)
矩阵分解中的梯度下降:详细实现方案(包含数学推导、代码实现和优化技巧) 矩阵分解是机器学习和数据科学中重要的技术,广泛应用于推荐系统、自然语言处理、图像处理等领域。梯度下降作为一种优化算法,在矩阵分解中常用于最小化目标函数以找到最佳的矩阵近似。本指南将详细…...