Ripple DOM Refs 实战指南:ref 赋值、回调 Ref、多 Ref、组件转发与 createRefKey 的完整解析

发布时间:2026/9/16 16:08:32
Ripple DOM Refs 实战指南:ref 赋值、回调 Ref、多 Ref、组件转发与 createRefKey 的完整解析 Ripple DOM Refs 实战指南ref 赋值、回调 Ref、多 Ref、组件转发与 createRefKey 的完整解析【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/ripple本篇基于 Ripple 官方文档 Referencing DOM Elements 展开系统讲解 Ripple 中捕获 DOM 节点的完整方案普通可变标识符 ref、track()产生的Trackedref、回调 ref 及其清理函数、数组形式多 ref、组件转发以及用于程序化装配 ref 的createRefKey()。读完之后你不仅会写 ref还能从源码层面理解 Ripple 是如何在挂载/卸载时赋值与清空 ref、如何在 props 展开spread中识别 ref以及 SSR 场景下的行为差异。Ref 的基本语法一个 ref 还是多个 refRipple 中的 ref 使用标准的 JSX 属性形态用来捕获某个元素背后的 DOM 节点。官方文档给出的两种语法如下语法用途ref{value}为当前元素或组件提供单个 ref。ref{[a, b]}为同一个元素提供多个 ref。ref 的值可以是以下几种形态回调函数callback挂载时接收 DOM 节点可返回清理函数track()创建的Tracked值把 DOM 节点存入响应式信号可在任意响应式作用域中读取可变标识符或成员表达式mutable identifier / member expression由框架在挂载时赋值、卸载时清空。官方文档给出的完整示例覆盖了这三种形态import { track } from ripple; export default function App() { let div: HTMLDivElement | undefined; const input trackHTMLInputElement | null(null); const state: { button?: HTMLButtonElement } {}; div ref{div}Hello world/div input ref{input} typetext / button ref{state.button}Save/button / }这里div是普通let变量input是Trackedstate.button是成员表达式。文档明确了生命周期语义可变 ref 在元素挂载时被赋值在元素卸载时被清空clear。这一语义在测试套件 ref.test.tsrx 中有直接验证例如“clears a plain let variable via ref{var} when the host element unmounts”用例用if (show)条件渲染一个div ref{div}初始断言div是HTMLDivElement实例点击按钮把show置为false后断言div变为null确认卸载清空行为成立。一个值得注意的优先级规则同样来自该测试文件当变量同时“看起来像”函数和标识符时Ripple 走函数路径function wins over setter当变量持有Tracked时走 Tracked 路径Tracked wins over setter。也就是说把track()返回值赋给普通变量再传给refRipple 依然能识别它是Tracked并正确写入。回调 Ref挂载赋值、卸载清理、函数工厂回调 ref 在元素挂载时接收 DOM 节点如果你在回调中返回一个清理函数它会在元素被移除时执行。官方文档示例export function App() { function setup(node: HTMLDivElement) { console.log(mounted, node); return () { console.log(unmounted, node); }; } div ref{setup}Hello world/div }回调也可以直接内联在属性位置顺便完成“赋值 日志 卸载复位”三件事export function App() { let div: HTMLDivElement | undefined; div ref{(node) { div node; console.log(mounted, node); return () { div undefined; }; }} Hello world /div }回调 ref 还有一个重要应用场景函数工厂。当第三方库替你返回 ref 回调、或者 ref 的初始化逻辑需要传参配置时工厂形式非常合适import { fadeIn } from some-library; export function App({ ms }) { return div ref{fadeIn({ ms })}Hello world/div }也就是说任何“接受配置、返回 ref 回调”的 API例如动画库、弹窗定位库都能直接作为ref值使用而fadeIn({ ms })在运行时求值返回的回调会被框架当作 ref 处理。多个 Ref同一个元素挂多个 ref当一个 DOM 元素需要被多个逻辑同时引用时ref接受数组形式。官方文档示例中同一个input同时挂了一个普通变量、一个Tracked和一个内联回调import { track } from ripple; export function App() { let input: HTMLInputElement | undefined; const trackedInput trackHTMLInputElement | null(null); input ref{[input, trackedInput, (node) console.log(node)]} / }数组中每一项都遵循前面所述的 ref 值规则可变标识符会被赋值/清空Tracked会写入/清空回调会被调用并注册其返回的清理函数。组件转发ref 作为普通 prop 传递Ripple 没有隐式的forwardRef机制——组件把ref{...}当作一个普通 prop 接收。你要么显式转发它要么把它包含在展开到宿主元素的那个 spread 里。官方文档的第一个示例用 spread 一把转发所有剩余 propsfunction Input({ id, ...rest }) { return input {id} {...rest} / } export function App() { let input: HTMLInputElement | undefined; Input idemail ref{input} / }Input把...rest包含ref整体展开到真正的input上于是外部的 ref 落到了宿主元素。测试套件中“works with spreading from composite component”用例验证了这一点父组件给复合组件Child传ref{componentRef}Child内部把...rest展开到pre上最终捕获的节点就是文档里第一个pre。另一种更常见的模式是命名 prop比如把 prop 叫inputRef。文档指出inputRef这类命名 prop 就是普通的组件 API prop如果你想让它生效为 ref需要在接收组件内部把它传给ref{...}export function Field({ inputRef, ...rest }) { return label Searchinput typesearch ref{inputRef} {...rest} / /label } export function App() { let input: HTMLInputElement | undefined; Field inputRef{input} placeholderSearch docs / }注意这里有两层语义差异测试文件里都有对应用例inputRef作为普通 prop 传给组件时它不会被当作 ref 自动生效必须由组件内部显式写ref{inputRef}“forwards a named ref prop explicitly through a component”用例但如果组件内部把该 prop 直接展开到宿主元素上如input {ref} {...rest} /运行时能识别出它是 ref 并正确挂载“forwards an ordinary named prop explicitly from a host spread”用例。类型层面RefValue类型由 Ripple 重新导出见 types/index.d.ts 中export type { RefValue } from tsrx/core/runtime/ref测试中用PropsWithExtras{ input_ref: RefValueHTMLInputElement }给组件 props 标注 ref 成员这是编写可复用组件时推荐的类型写法。createRefKey把 ref 放进任意展开的 props 对象createRefKey()是 Ripple 提供的一个辅助 API它创建一个唯一的对象键当含有该键的对象被展开到元素上时Ripple 会把对应值识别为 ref。这在 ref 需要程序化装配时特别有用——比如你有一个动态构造的 props 对象想同时包含id、value和一个 ref 回调import { createRefKey, track } from ripple; export function App() { let [value] track(); let input: HTMLInputElement | undefined; const props { id: example, value, [createRefKey()]: (node: HTMLInputElement) { input node; const onInput () { value node.value; console.log(value); }; node.addEventListener(input, onInput); return () { input undefined; node.removeEventListener(input, onInput); }; }, }; input typetext {...props} / Input {...props} / / } function Input({ id, value, ...rest }) { return input typetext {id} {value} {...rest} / }这个例子里 ref 回调同时做了两件事把节点存入input变量并手动绑定input事件监听卸载时通过返回的清理函数移除监听。注意这个 props 对象同时被展开到了宿主input和复合组件Input上——两条路径都能识别 ref这在测试用例 “should handle spreading into composite refs” 中被验证断言 ref 回调恰好被调用了两次host 与 composite 各一次。源码层面ref 键为什么能被识别从源码结构看createRefKey()在客户端的实现非常轻量——runtime.js 中它就是一个返回带固定描述的 Symbol 的工厂函数export function ref_prop() { return Symbol(REF_PROP); }而REF_PROP常量定义为字符串ref见 constants.js并在客户端入口以createRefKey的名字导出index-client.jsexport { ref_prop as createRefKey } from ./internal/client/runtime.js;也就是说createRefKey()每次返回一个description ref的全新 SymbolSpread 更新逻辑据此区分“真正的 ref”和“普通属性”。识别发生在元素 spread 的处理函数apply_element_spread中render.js。其核心机制可以概括为三点Symbol 键扫描遍历 props 对象的所有自有 Symbol只处理description ref的键即createRefKey()生成的键为每个 ref 建立独立的响应式 effectcreate_spread_ref_effectref 函数变化或旧 effect 已销毁时才会销毁重建字符串键中的 ref 值检测对普通字符串键用is_ref_prop(value)判断值是否为 ref 属性。测试用例 “reports ordinary functions and ref objects as non named-ref props” 明确了边界普通函数和{ current: null }这类 ref-like 对象都不会被误判为 refisRefProp(() {})与isRefProp({ current: null })均返回falseRipple 识别的是带标记的 ref 值而非任意函数卸载对称性当某个 ref 键从 props 中消失时对应的 effect 被销毁同时之前为该键注册的事件监听也会被移除remove_listeners分支。测试中“clears the Tracked when the host element unmounts”“clears a component ref prop when a host spread changes it to a regular prop”等用例覆盖了 ref 从 spread 中移除、ref 与普通属性互相转换等边角场景确认赋值与清空是严格配对的。服务端行为SSR 下 ref 是 no-op从源码结构看ref 是纯客户端概念服务端运行时把createRefKey导出为noopindex-server.jsexport const createRefKey noop;因此在使用 SSR 的项目中含有createRefKey()的 props 对象在服务端渲染阶段不会造成任何副作用DOM 节点捕获只在客户端挂载hydrate/挂载后发生。写跨端组件时依赖 ref 的逻辑都应放在回调或effect中而不是组件顶层同步代码里。与响应式系统的结合Ripple 的 ref 与响应式体系是打通的这也是它与“命令式 ref 对象”方案的主要差异点Trackedref 让 DOM 节点本身成为响应式数据。const input trackHTMLInputElement | null(null)之后任何effect、事件处理函数或渲染表达式都可以读取input的当前值节点挂载/卸载会自动反映到该信号上测试 “captures a host element into a Tracked via ref{tracker}” 与 “clears the Tracked when the host element unmounts” 分别验证了写入与清空读取时机要注意组件的 setup 阶段先于 DOM 节点创建所以在 setup 中同步读取let div仍是undefined测试中统一用effect(() { captured div ?? null; })来观察挂载后的值见 ref.test.tsrx 中capturing a host element用例的注释说明。这是一个容易踩的坑把 DOM 节点存入变量后请在 effect 或事件回调中读取不要在组件初始化代码里同步断言它非空。小结Ripple 的 DOM refs 设计可以归纳为一句话ref 值就是数据框架负责赋值与清理的配对。核心要点回顾场景写法关键语义简单捕获ref{div}let变量或成员表达式挂载赋值卸载清空响应式捕获ref{trackT \| null(null)}写入/清空Tracked可被响应式系统读取带清理逻辑ref{(node) { ...; return cleanup }}挂载调用卸载执行返回的清理函数库提供的 refref{fadeIn({ ms })}函数工厂返回 ref 回调直接可用一个元素多个 refref{[a, b, cb]}每项独立按规则处理组件转发Input ref{x} / 组件内{...rest}或ref{inputRef}ref 是普通 prop需显式落到宿主元素程序化装配props[createRefKey()] cbspread 时按description ref的 Symbol 键识别以上所有行为都可以直接对照 ref.test.tsrx 中的 20 余个测试用例复现验证识别与 spread 更新的实现细节则集中在 render.js 与 runtime.js可作为进一步阅读源码的入口。【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/ripple创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询