新闻详情

新闻详情

首页 / 资讯中心 / 详情

Ripple 组件系统实战指南:组件检测、生命周期、作用域嵌套与 Portal 传送门

发布时间:2026/9/16 18:55:45来源:尧图网络
Ripple 组件系统实战指南:组件检测、生命周期、作用域嵌套与 Portal 传送门
Ripple 组件系统实战指南组件检测、生命周期、作用域嵌套与 Portal 传送门【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/rippleRipple 是一个优雅的 TypeScript UI 框架其组件系统建立在 TSRX带编译期转换的 TSX 超集之上组件是普通的 JavaScript/TypeScript 函数由编译器识别并按需重写为响应式渲染块。本文以 Ripple 官方指南 Components in Ripple 为主体结合框架运行时源码系统讲解组件如何被检测与编译、生命周期如何运行、子内容children如何传递、组件如何作为 props 传递、作用域嵌套的规则与坑以及 Portal 组件如何把内容传送到 DOM 任意位置。读完本文你将能编写符合 Ripple 组件模型的高质量组合式组件并理解其背后的编译与运行时原理。组件的检测Detection什么函数会被当作组件编译在 Ripple 中组件就是普通函数编译器位于 packages/tsrx-ripple/src通过“直接调用保持普通辅助函数语义”的规则来决定是否启用组件编译直接调用的 PascalCase 辅助函数如StatusCode()、FormatName()不会被当作组件而是保留为普通函数调用组件编译适用于被用作组件或渲染入口的函数以及未被直接调用、但返回原生 TSRX 的函数。从源码看这一判断逻辑由 is_tsrx_component_function 完成它把“原生 TSRX 函数节点”node.metadata?.native_tsrx_function识别为组件同时也会把函数体为JSXCodeBlock即 TSRX 的{ ... }代码块体的普通函数声明/表达式识别为组件。换句话说一个函数“长得像组件”返回 TSRX、以组件形式被使用编译器就会给它应用组件级转换直接以普通函数语义调用的工具函数则不会被卷入响应式重写。这种设计让开发者可以自由地用小写辅助函数整理逻辑而 PascalCase 的组件则获得完整的响应式生命周期。类型层面组件的形态由 types/index.d.ts 中的ComponentT Recordstring, any定义(props: T) Renderable | void即接收 props、返回可渲染值或void的函数。组件生命周期根作用域只运行一次副作用请放进 effect()Ripple 的组件生命周期与 Vue / Svelte / Solid 类似根作用域只运行一次组件函数的顶层代码相当于 Vue/Svelte/Solid 中的 “setup” 作用域仅在组件初始化时执行子作用域可能重跑语句容器以及if、for等块只要内部包含响应式变量就可能随依赖变化而重新执行。因此官方文档给出明确建议组件内部只写纯代码把所有副作用放进effect()确保副作用只在预期时机运行。文档为此给出了两条术语定义Pure纯函数不应产生副作用Side-effect副作用持久的、外部可观察的状态变化。这一生命周期模型在运行时实现中非常清晰。if、for、动态插值等都会被编译成可重跑的 block渲染块运行时代码见 blocks.jsrender()创建普通渲染块branch()创建分支块用于if、动态组件等而effect()与pre_effect()分别创建EFFECT_BLOCK与PRE_EFFECT_BLOCK。调度器在 runtime.js 中按顺序处理 pre-effect → 渲染块 → effect从而保证pre-effect 先于渲染执行如trackAsync发请求前的准备渲染块只更新依赖变化的最小 DOM 范围effect 在渲染之后执行适合做副作用DOM 操作、事件监听、日志等。这也是“根作用域只跑一次、块可重跑”的底层原因组件函数的顶层代码只编译为一次性执行而含响应式变量的块则被拆成可订阅、可重跑的独立 block。子内容Children嵌套即传递隐式与显式两种用法要把元素嵌套进组件内部只需像写 HTML 一样嵌套。默认情况下Ripple 会把嵌套内容以childrenprop 暴露给组件渲染时使用{props.children}解构 props 后可直接写{children}。import type { Children } from ripple; function Card(props: { children: Children }) { return div classcard{props.children}/div } export function App() { function children() { return pCard content here/p } // Use implicitly... Card pCard content here/p /Card // or pass children explicitly as a prop. Card {children} / / }从类型定义看Children是一个联合类型见 types/index.d.tsexport type Children TSRXElement | Component | string | number | boolean | null | undefined;也就是说 children 既可以是原生 TSRX 元素、组件也可以是字符串、数字、布尔值或空值。这种宽容的类型让{children}在模板中能覆盖绝大多数内容组合场景。把组件作为 props 传递显式传参遵循词法作用域组件只能作为显式 props 传递给其他组件。你可以在任何词法作用域声明组件——包括在组合式组件元素内部——但必须把它作为 prop 传给需要它的组件。::: warning 作用域规则 组件遵循普通词法作用域。父组件看不到声明在子组件作用域内的组件。声明在组合式组件元素内部的组件只对该元素的子级可见对元素自身的组件不可见。 :::基础示例定义组件并通过 props 传入import type { Component } from ripple; function Composite({ PropComp }: { PropComp: Component }) { return PropComp / } function Separate() { return pIm a separate component./p } export function App() { return Composite PropComp{Separate} / }这里Separate以PropCompprop 传入CompositeComposite内部用PropComp /渲染它。运行时层面PropComp /会被编译为动态组件调用由 composite.js 中的composite()处理它会先get()读取组件引用若当前值是函数则作为常规组件渲染render_component若是一个字符串标签名则退化为创建对应自定义元素createElementNS/createElement并处理 props 的展开与 children 的挂载——这意味着动态组件语法同时兼容“真组件”和“动态标签名/自定义元素”。常规组件的渲染入口在 component.jsrender_component会校验传入的是函数且不是 TSRX 元素否则抛出Invalid component type随后run_component通过push_component()/pop_component()维护组件栈再调用render_value(fn(props), anchor, block)把组件的返回值渲染到锚点。这也是“组件本质是接收 props 并返回可渲染值的函数”这一模型的直接体现。实战示例基于子内容组合的 Card 组件下面这个模式对应 Vue/Web Components 的 “slots”、React 的 “render props”、Svelte 的 “snippets”。Card组件接收children以及可选的Header、Footer组件 prop用if按需渲染头部和底部import type { Children, Component } from ripple; function Card({ children, Header, Footer, }: { children: Children; Header?: Component; Footer?: Component; }) { return fieldset if (Header) { Header / hr / / } {children} if (Footer) { hr / Footer / / } /fieldset; } function CustomHeader() { return h1Card Title/h1 } function CustomFooter() { return buttonCancel/button buttonOK/button /; } export function App() { return Card Header{CustomHeader} Footer{CustomFooter} pCard content here/p /Card; }注意if (Header)是对“组件 prop 是否存在”的响应式判断如果Header是响应式值则if块会随之重跑即便它是静态值if也保证了该分支在条件不成立时完全不渲染。这种“插槽化”的组合方式把可变部位Header/Footer和固定部位children彻底解耦是构建可复用布局组件的标准做法。组件作用域与嵌套正确姿势与常见误区组件可以在任意词法作用域声明包括组合式组件元素内部但只在声明所在作用域内可见——父组件无法访问声明在子作用域内的组件。✅ 正确在组合式元素内部声明组件并传给同级子组件声明在组合式组件元素内部的组件可以在同作用域内的嵌套组件调用中作为 prop 传递import type { Component } from ripple; function Inner({ Greeting }: { Greeting: Component }) { return div classinnerGreeting //div } function Outer({ children }: { children: Children }) { return div classouter{children}/div } export function App() { Outer{ function HelloGreeting() { return pHello from inside!/p } // It can be passed as a prop to Inner, which is also in this scope Inner Greeting{HelloGreeting} / }/Outer }关键点HelloGreeting声明在Outer的 children 作用域内Inner也在同一作用域内被调用因此Greeting{HelloGreeting}完全合法。❌ 错误把子作用域内的组件传给父组件声明在组合式元素 children 内的组件对父组件自身不可见——它只存在于子作用域import type { Component } from ripple; function Outer({ Footer }: { Footer: Component }) { // Outer expects Footer as a prop return div classouterFooter //div } export function App() { // ❌ WRONG — Footer is declared inside Outers children, // but Outer cannot see it. Footer is not in scope for the // Outer component call. Outer {Footer}{ function Footer() { return buttonOK/button } pChild content/p }/Outer }Footer声明在Outer的子作用域里而Outer这个调用点所在的作用域看不到它因此{Footer}简写等价于Footer{Footer}会解析失败或指向不存在的绑定。这个反例与文档开头的“组件遵循普通词法作用域”警告完全一致作用域是静态的、词法的模板不会让子作用域内的名字“冒泡”到父级。响应式 PropsReactive Props组件的 props 与响应式系统深度集成当传入的 prop 是响应式值如track()创建的 tracked value、派生值等时读取该 prop 的渲染块会自动订阅它prop 变化时触发最小范围的块重跑。关于响应式变量的创建、派生与订阅机制请参见仓库中的 Reactivity 指南。Prop 简写Prop ShorthandsRipple 支持三种便捷的 prop 写法对象展开、同名简写、以及二者结合// Object spread div {...properties}Content/div // Shorthand props (when variable name matches prop name) div {onClick} {id}Content/div // Equivalent to: div onClick{onClick} id{id}Content/div{...properties}会把对象的所有键值展开为属性运行时由 blocks.js 中的render_spread实现它会将对象整体作为一个响应式源键的变化触发属性级更新{onClick}、{id}是onClick{onClick}、id{id}的语法糖要求变量名与 prop 名一致二者可以混用。展开写法在需要把一整包配置动态传给元素尤其是包装第三方组件/原生元素时非常实用。Portal 组件把内容传送到 DOM 任意位置Portal组件允许你把内容渲染传送到 DOM 树的任意位置跳出常规组件层级。这对模态框、工具提示、通知等需要“悬浮”在页面顶层、不受父级 overflow / z-index / transform 影响的 UI 尤其有用。import { Portal } from ripple; export function App() { return div classapp h1My App/h1 {/* This will render inside document.body, not inside the .app div */} Portal target{document.body} div classmodal h2I am rendered in document.body!/h2 pThis content escapes the normal component tree./p /div /Portal /div; }上面的示例中模态框内容虽然书写在.app内部但实际渲染位置是document.body——它在 DOM 结构上完全脱离.app从而避免被祖先元素的样式或裁剪影响。Portal 的运行时实现从源码看Portal是一个真正的组件而非编译魔法。它的实现位于 portal.js通过tsrx_element返回一个 TSRX 元素渲染时主要做四件事目标元素的事件委托接管用独立的render()块对props.target执行handle_root_events且只在 target 变化时重跑——children 更新不会重复释放/获取目标上的委托监听内容块的挂载与销毁在目标元素内追加一个文本锚点用branch()把 children 渲染到该锚点target 或 children 变化时销毁旧块、重建新块并记录dom_start/dom_end以便清理清理钩子块卸载时移除锚点并删除传送内容的 DOM 区间remove_block_dom客户端专属 处理水合Portal 不参与服务端水合渲染期间会临时关闭hydrating状态并在结束后恢复。该组件从 runtime/index-client.js 以export { Portal }导出与effect、on、track等客户端 API 并列。用测试验证 Portal 行为仓库测试 portal.test.tsrx 直接验证了上述语义内容出现在target内而不是组件容器内expect(target.querySelector(.test-portal).textContent).toBe(Portal works!)传document.body时内容渲染到 body组件卸载后 Portal 内容被清理多 Portal 可分别指定不同 target各自渲染到对应目标。这些测试表明Portal 是框架提供的、经过验证的标准能力可以直接用于生产组件如全局弹层、下拉浮层的搭建。小结Ripple 的组件系统可以用一句话概括组件是返回 TSRX 的普通函数由编译器识别、由运行时块级渲染驱动。本文覆盖了组件检测规则PascalCase 组件 vs 普通辅助函数、生命周期根作用域一次执行 子块响应式重跑 副作用进effect()、children 组合、组件作为显式 props 传递、词法作用域嵌套的正确与错误姿势、prop 简写以及 Portal 传送门的实现与验证。把这套模型与 控制流、响应式、事件 指南结合阅读即可完整掌握 Ripple 的组件化开发范式。【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/ripple创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Snipaste截图工具的高效应用与技巧 2026/9/16 19:34:51

Snipaste截图工具的高效应用与技巧

1. Snipaste:重新定义截图工具的效率革命作为一名每天需要处理大量图文信息的从业者,我几乎尝试过市面上所有的截图工具。直到三年前偶然发现Snipaste,它彻底改变了我处理多任务的工作方式。这款不到20MB的小工具,用"截图贴图…

阅读更多 →
DeepSeek-R1+Dify+BGE-M3:从零搭建PDF智能客服的RAG实践指南 2026/9/16 19:34:51

DeepSeek-R1+Dify+BGE-M3:从零搭建PDF智能客服的RAG实践指南

DeepSeek-R1、Dify、BGE-M3这三个词放在一起,最初我只是想做一个能读PDF的智能客服:把产品手册、售后政策这些文档丢进去,客户问什么,机器人就从里面找答案。二十六页的PDF,37个常见问题,我原以为用大模型A…

阅读更多 →
Halcon工业质检实战:尺寸测量、缺陷检测等5个核心案例 2026/9/16 19:34:51

Halcon工业质检实战:尺寸测量、缺陷检测等5个核心案例

1. 为什么不直接上深度学习?聊聊Halcon在工业质检里的真实定位先回应一个我经常被问的问题:既然深度学习这么火,为什么还要用Halcon做缺陷检测和尺寸测量?直接上神经网络难道不香吗?答案是:产线不等人&…

阅读更多 →
Halcon共线XLD轮廓合并:union_collinear_contours_xld参数详解与实战 2026/9/16 19:34:51

Halcon共线XLD轮廓合并:union_collinear_contours_xld参数详解与实战

1. 为什么你需要认真对待 union_collinear_contours_xld做机器视觉Halcon开发的朋友,肯定遇到过这个场景:用threshold和edges_sub_pix提取边缘后,得到了一堆断断续续的XLD轮廓,明明是一条完整的直线边缘,却被分成十几段…

阅读更多 →
lefthook 远程配置共享指南:用 `remotes` 跨仓库复用 Git Hooks 配置 2026/9/16 19:34:51

lefthook 远程配置共享指南:用 `remotes` 跨仓库复用 Git Hooks 配置

lefthook 远程配置共享指南:用 remotes 跨仓库复用 Git Hooks 配置 【免费下载链接】lefthook Fast and powerful Git hooks manager for any type of projects. 项目地址: https://gitcode.com/GitHub_Trending/le/lefthook 导读 本文讲解 lefthook 的 rem…

阅读更多 →
3 行配置实现 Codex 多仓库记忆隔离:Hindsight 记忆银行布局实战 2026/9/16 19:31:50

3 行配置实现 Codex 多仓库记忆隔离:Hindsight 记忆银行布局实战

3 行配置实现 Codex 多仓库记忆隔离:Hindsight 记忆银行布局实战 【免费下载链接】hindsight Hindsight: Agent Memory That Learns 项目地址: https://gitcode.com/GitHub_Trending/hindsight2/hindsight 让 Codex 修 API 仓库里的一个查询 bug,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞