ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Cherry Studio 工程实践:彻底搞懂 RSC Props 按引用去重,消灭重复序列化冗余

Cherry Studio 工程实践:彻底搞懂 RSC Props 按引用去重,消灭重复序列化冗余 Cherry Studio 工程实践彻底搞懂 RSC Props 按引用去重消灭重复序列化冗余【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio导读本指南围绕 Cherry Studio 仓库中随附的 Vercel React 性能最佳实践技能规则 server-dedup-props.md 展开深入讲解 React Server ComponentsRSC跨服务端/客户端边界序列化时按对象引用去重的核心机制。读完本文你将掌握为什么usernames与usernames.toSorted()同时传给客户端组件会让网络载荷翻倍、哪些数据类型的重复引用影响最大、以及如何在服务端与客户端之间正确划分数据变换职责从而在不改变功能的前提下显著减小 RSC 传输体积。一、背景RSC 边界的序列化与去重机制在 React Server Components 架构中服务端组件渲染完成后需要把传递给客户端组件use client的 props 序列化为字符串嵌入 HTML 响应以及后续的 RSC 请求中。这部分序列化数据会直接叠加到页面体积和加载时间上——正如姊妹规则 server-serialization.md 所述服务端与客户端的边界会把对象的所有属性序列化成字符串因此size matters a lot只应传递客户端真正用到的字段。关键机制在于RSC → 客户端的序列化去重是按对象引用reference而非按值value进行的。也就是说同一个引用被传给客户端组件只序列化一次一旦产生新的引用即使内容完全相同就会再次完整序列化一份。这条规则正是本仓库技能库中server-前缀Server-Side PerformanceHIGH 优先级类别的第 3.2 条规则定位为 LOW 影响、用于通过避免重复序列化来减小网络载荷。二、反模式示例服务端重复传递派生数组先看最常见的错误写法。下面这段代码把同一个数组的原始版本和排序版本同时传给了客户端组件// RSC: sends 6 strings (2 arrays × 3 items) ClientList usernames{usernames} usernamesOrdered{usernames.toSorted()} /这里的usernames是一个长度为 3 的字符串数组。usernames.toSorted()会创建一个全新的数组引用其内容虽然与原始数组完全一致但引用不同。因此 RSC 序列化器无法识别它们其实是同一批数据结果原始数组序列化一次3 个字符串排序后的新数组引用再次序列化一次又是 3 个字符串。最终网络载荷中出现了6 个字符串而客户端实际只需要其中一份数据。三、正确做法数据变换下放到客户端正确的做法是让服务端只传递一次原始引用把排序这类派生变换放到客户端组件内完成// RSC: send once ClientList usernames{usernames} / // Client: transform there use client const sorted useMemo(() [...usernames].sort(), [usernames])这样 RSC 只序列化 3 个字符串客户端在本地完成排序。这里有两个细节值得注意客户端排序用[...usernames].sort()而不是usernames.sort().sort()是原地in-place修改方法会直接改动 props 数组破坏 React 的不可变模型。这一点与技能库中 js-tosorted-immutable.md 规则的警告一致——Props/state 的变更会破坏 React 的不可变模型React 期望 props 与 state 被视为只读。在支持新式方法的现代浏览器Chrome 110、Safari 16、Firefox 115、Node.js 20中也可改用usernames.toSorted()旧环境则用[...items].sort()兜底。客户端派生用useMemo包裹useMemo(() [...usernames].sort(), [usernames])保证只有当usernames引用变化时才重新排序避免每次渲染都产生新的排序结果。四、嵌套去重的行为差异按数据类型评估影响去重机制是递归生效的但不同数据类型的重复成本差异极大规则文档给出了两组对照示例// string[] - duplicates everything usernames{[a,b]} sorted{usernames.toSorted()} // sends 4 strings // object[] - duplicates array structure only users{[{id:1},{id:2}]} sorted{users.toSorted()} // sends 2 arrays 2 unique objects (not 4)对影响程度可以做出如下判断string[]、number[]、boolean[]这类原始类型数组影响为HIGH——数组本身与其中的全部原始值都会被完整重复序列化。因为原始值没有引用概念无法通过引用去重只能逐字复制。object[]这类对象数组影响为LOW——数组结构外层引用会被重复序列化但嵌套对象是按引用去重的所以toSorted()前后数组里的对象元素仍是同一批引用不会被二次复制。因此是2 个数组 2 个唯一对象而不是 4 份对象。这条结论可以直接指导我们做成本决策当数据是原始类型数组时在服务端多做一次派生变换的代价是成倍的网络字节当数据是对象数组时代价相对可控但仍不值得白白浪费。五、打破去重的操作清单识别新引用制造机只要产生了新引用去重即失效。规则文档给出了一份明确的清单数组类创建新引用.toSorted().filter().map().slice()展开语法[...arr]对象类创建新引用展开语法{...obj}Object.assign()structuredClone()JSON.parse(JSON.stringify())把这些操作放在服务端渲染路径上就意味着每次调用都会生成一个无法与原始数据共享序列化的新对象。特别注意JSON.parse(JSON.stringify())——它不仅是深拷贝而且会丢弃函数、undefined、循环引用等无法序列化的值同时产生一个彻底的全新引用是去重的最大杀手。六、更多对照示例对象属性拆分同样有害除了数组派生把对象属性拆出来单独传递同样会造成重复序列化// ❌ Bad C users{users} active{users.filter(u u.active)} / C product{product} productName{product.name} / // ✅ Good C users{users} / C product{product} / // Do filtering/destructuring in client反例中C users{users} active{users.filter(...)} /users与users.filter(...)是两个引用序列化两份C product{product} productName{product.name} /整个product对象序列化一次product.name这个字符串又单独序列化一次——而它明明已经是product对象内部的一个属性。正确写法是只传原始引用users与product过滤、解构等派生工作全部交给客户端组件内部完成。七、规则例外何时允许在服务端传派生数据规则文档特别给出了例外情况当变换本身非常昂贵或者客户端根本不需要原始数据时应当在服务端传递派生后的结果。例如变换需要访问服务端才有的大数据、需要多次网络往返或高计算量此时在客户端重复计算反而更浪费客户端只需要已排序/已过滤的结果不需要原始数组——此时只传一份派生结果是最优的因为根本没有两份数据需要去重。判断标准可以归纳为一句话只有当同一份原始数据要被客户端同时以多种形态使用时重复序列化才是需要避免的浪费如果客户端只需要单一派生形态直接在服务端算好再传。八、协同规则把 RSC 边界数据传输压到最低本规则并非孤立存在它与技能库 SKILL.md 中 Server-Side PerformanceHIGH分类下的多条规则构成一套完整打法减少总量先按 server-serialization.md 的规则只传递客户端实际用到的字段。例如服务端fetchUser()返回 50 个字段、客户端只用name时直接传Profile name{user.name} /而不是整个user对象——把 50 个字段压到 1 个字段。消除重复再按本规则server-dedup-props同一个引用只传一次派生形态留给客户端。请求级去重如果担心客户端重复渲染导致多次派生计算可结合 client-swr-dedup.md 使用 SWR 对数据请求本身去重多个组件实例共享同一次请求。服务端去重若同一请求内多处需要相同数据可参考 server-cache-react.md 用React.cache()做请求内去重注意其基于Object.is的浅比较内联对象会永远 miss。九、在 Cherry Studio 仓库中的落地方式Cherry Studio 仓库将这套规则以技能skill形式固化在 .agents/skills/vercel-react-best-practices 目录下每条规则一个 Markdown 文件带title/impact/impactDescription/tags的 frontmatter通过 README.md 中描述的pnpm build编译合并进AGENTS.md并支持pnpm validate校验、pnpm extract-tests抽取测试用例。规则文件命名前缀决定了所属分类server-对应 Server-Side Performancejs-对应 JavaScript Performanceclient-对应 Client-Side Data Fetchingrerender-对应 Re-render Optimization等等。Impact 分级CRITICAL / HIGH / MEDIUM-HIGH / MEDIUM / LOW-MEDIUM / LOW用于指导自动化重构的优先级本规则属于 LOW 增量优化通常在完成 HIGH/CRITICAL 级优化如并行抓取、包体积、序列化瘦身之后作为最后一公里的净载荷削减手段落地。在实际开发中审查任何从 Server Component 传到use client组件的 props 时都可以对照本文的检查清单同一份数据是否被传了多次派生变换是否留在了服务端如果是就把变换挪到客户端让 RSC 序列化器用引用去重为你省下每一份重复的字节。【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表