|
| 1 | +--- |
| 2 | +title: Ripple.js - 优雅的 TypeScript UI 框架 |
| 3 | +published: 2026-01-09 |
| 4 | +description: 深入解析 Ripple.js 的设计原理、核心特性,以及与 React、Svelte、Solid 的对比分析 |
| 5 | +tags: [TypeScript, UI Framework, Frontend, Reactive] |
| 6 | +category: Frontend |
| 7 | +draft: false |
| 8 | +--- |
| 9 | + |
| 10 | +## 简介 |
| 11 | + |
| 12 | +[Ripple](https://www.ripplejs.com/) 是一个优雅的、编译器驱动的 TypeScript UI 框架,由 Dominic Gannaway ([@trueadm](https://github.com/trueadm)) 创建。Dominic 是前端领域的资深专家,曾参与 Inferno、React Hooks、Lexical 以及 Svelte 5 的开发。 |
| 13 | + |
| 14 | +Ripple 结合了 React、Solid 和 Svelte 的优点,提供了一种基于 JSX 超集的全新开发体验。它使用独特的 `.ripple` 文件扩展名,原生支持 TypeScript,并引入了创新的响应式语法。 |
| 15 | + |
| 16 | +> **注意**: Ripple 目前仍处于早期开发阶段,尚未准备好用于生产环境。 |
| 17 | +
|
| 18 | +## 核心特性 |
| 19 | + |
| 20 | +### 1. 响应式状态管理 |
| 21 | + |
| 22 | +Ripple 使用 `track()` 函数创建响应式变量,通过 `@` 符号访问和修改值: |
| 23 | + |
| 24 | +```tsx |
| 25 | +import { track } from 'ripple'; |
| 26 | + |
| 27 | +export component Counter() { |
| 28 | + let count = track(0); |
| 29 | + let double = track(() => @count * 2); // 派生响应式值 |
| 30 | + |
| 31 | + <div> |
| 32 | + <p>{"Count: "}{@count}</p> |
| 33 | + <p>{"Double: "}{@double}</p> |
| 34 | + <button onClick={() => @count++}>{"Increment"}</button> |
| 35 | + </div> |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +这种设计的优势在于: |
| 40 | +- 显式的响应式追踪,避免意外的重渲染 |
| 41 | +- `@` 语法清晰地标识响应式值的读写 |
| 42 | +- 支持派生状态,自动追踪依赖 |
| 43 | + |
| 44 | +### 2. 组件定义语法 |
| 45 | + |
| 46 | +不同于 React 的函数返回 JSX,Ripple 使用 `component` 关键字定义组件,模板直接作为语句书写: |
| 47 | + |
| 48 | +```tsx |
| 49 | +component Button(props: { text: string, onClick: () => void }) { |
| 50 | + <button onClick={props.onClick}> |
| 51 | + {props.text} |
| 52 | + </button> |
| 53 | +} |
| 54 | + |
| 55 | +export component App() { |
| 56 | + <Button text="Click me" onClick={() => console.log("Clicked!")} /> |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +**重要**: Ripple 中所有文本内容必须包裹在表达式 `{}` 中: |
| 61 | + |
| 62 | +```tsx |
| 63 | +// 错误写法 |
| 64 | +<div>Hello World</div> |
| 65 | + |
| 66 | +// 正确写法 |
| 67 | +<div>{"Hello World"}</div> |
| 68 | +``` |
| 69 | + |
| 70 | +### 3. 原生控制流 |
| 71 | + |
| 72 | +Ripple 支持在模板中直接使用 JavaScript 原生控制流语句: |
| 73 | + |
| 74 | +```tsx |
| 75 | +component TodoList({ todos }) { |
| 76 | + <ul> |
| 77 | + for (const todo of todos; index i; key todo.id) { |
| 78 | + <li>{todo.text}{" at index "}{i}</li> |
| 79 | + } |
| 80 | + </ul> |
| 81 | + |
| 82 | + if (todos.length === 0) { |
| 83 | + <p>{"No todos yet!"}</p> |
| 84 | + } else { |
| 85 | + <p>{"Total: "}{todos.length}</p> |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +还支持 `switch` 语句和 `try-catch` 错误边界: |
| 91 | + |
| 92 | +```tsx |
| 93 | +component StatusIndicator({ status }) { |
| 94 | + switch (status) { |
| 95 | + case 'loading': |
| 96 | + <p>{'Loading...'}</p> |
| 97 | + break; |
| 98 | + case 'success': |
| 99 | + <p>{'Success!'}</p> |
| 100 | + break; |
| 101 | + case 'error': |
| 102 | + <p>{'Error!'}</p> |
| 103 | + break; |
| 104 | + default: |
| 105 | + <p>{'Unknown status'}</p> |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +### 4. 响应式集合 |
| 111 | + |
| 112 | +Ripple 提供了完整的响应式集合类型: |
| 113 | + |
| 114 | +```tsx |
| 115 | +// 响应式数组 |
| 116 | +const items = #[1, 2, 3]; |
| 117 | +// 或 |
| 118 | +const items = new TrackedArray(1, 2, 3); |
| 119 | + |
| 120 | +// 响应式对象 |
| 121 | +const obj = #{a: 1, b: 2}; |
| 122 | +// 或 |
| 123 | +const obj = new TrackedObject({a: 1, b: 2}); |
| 124 | + |
| 125 | +// 响应式 Map |
| 126 | +const map = new TrackedMap([[1, 'a'], [2, 'b']]); |
| 127 | + |
| 128 | +// 响应式 Set |
| 129 | +const set = new TrackedSet([1, 2, 3]); |
| 130 | +``` |
| 131 | + |
| 132 | +### 5. 作用域样式 |
| 133 | + |
| 134 | +组件支持内置的作用域 CSS: |
| 135 | + |
| 136 | +```tsx |
| 137 | +component StyledCard() { |
| 138 | + <div class="card"> |
| 139 | + <h1>{"Styled Content"}</h1> |
| 140 | + </div> |
| 141 | + |
| 142 | + <style> |
| 143 | + .card { |
| 144 | + background: #f0f0f0; |
| 145 | + padding: 1rem; |
| 146 | + border-radius: 8px; |
| 147 | + } |
| 148 | + h1 { |
| 149 | + color: #333; |
| 150 | + } |
| 151 | + </style> |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +## 工作原理 |
| 156 | + |
| 157 | +### 编译器驱动 |
| 158 | + |
| 159 | +Ripple 是一个编译器驱动的框架。`.ripple` 文件在构建时被编译成优化的 JavaScript 代码。这种方式带来了几个优势: |
| 160 | + |
| 161 | +1. **细粒度更新**: 不使用虚拟 DOM,而是在编译时分析依赖关系,生成精确的 DOM 更新代码 |
| 162 | +2. **更小的运行时**: 许多工作在编译时完成,运行时代码更精简 |
| 163 | +3. **类型安全**: 编译器可以进行完整的 TypeScript 类型检查 |
| 164 | + |
| 165 | +### 响应式系统 |
| 166 | + |
| 167 | +Ripple 的响应式系统基于 `Tracked<V>` 对象: |
| 168 | + |
| 169 | +```tsx |
| 170 | +// track() 创建一个 Tracked<V> 对象 |
| 171 | +let count = track(0); |
| 172 | + |
| 173 | +// @count 读取值(建立依赖追踪) |
| 174 | +console.log(@count); // 0 |
| 175 | + |
| 176 | +// @count++ 修改值(触发更新) |
| 177 | +@count++; |
| 178 | +``` |
| 179 | + |
| 180 | +这种设计确保了: |
| 181 | +- 只有实际被读取的值才会建立依赖关系 |
| 182 | +- 更新是细粒度的,只影响真正依赖该值的 DOM 节点 |
| 183 | +- 可以使用 `untrack()` 显式跳过依赖追踪 |
| 184 | + |
| 185 | +### 模板作为词法作用域 |
| 186 | + |
| 187 | +Ripple 的独特之处在于模板本身就是词法作用域,可以在其中声明变量和执行语句: |
| 188 | + |
| 189 | +```tsx |
| 190 | +component TemplateScope() { |
| 191 | + <div> |
| 192 | + // 在模板内声明变量 |
| 193 | + const message = "Hello"; |
| 194 | + let count = 42; |
| 195 | + |
| 196 | + console.log("This runs during render"); |
| 197 | + |
| 198 | + <h1>{message}</h1> |
| 199 | + <p>{"Count: "}{count}</p> |
| 200 | + |
| 201 | + // 嵌套作用域 |
| 202 | + <section> |
| 203 | + const sectionData = "Nested scope"; |
| 204 | + <p>{sectionData}</p> |
| 205 | + </section> |
| 206 | + </div> |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +## 与其他框架对比 |
| 211 | + |
| 212 | +### vs React |
| 213 | + |
| 214 | +| 特性 | Ripple | React | |
| 215 | +|------|--------|-------| |
| 216 | +| 响应式 | 内置 `track()` + `@` 语法 | useState/useEffect | |
| 217 | +| DOM 更新 | 细粒度直接更新 | 虚拟 DOM diff | |
| 218 | +| 样式 | 内置作用域 CSS | 需要 CSS-in-JS 库 | |
| 219 | +| 组件定义 | `component` 关键字 | 函数返回 JSX | |
| 220 | +| 控制流 | 原生 if/for/switch | JSX 表达式 / map() | |
| 221 | +| TypeScript | 原生支持 | 需要配置 | |
| 222 | + |
| 223 | +**Ripple 优势**: |
| 224 | +- 无需学习 Hooks 规则 |
| 225 | +- 更直观的响应式语法 |
| 226 | +- 更好的性能(无虚拟 DOM 开销) |
| 227 | +- 更小的包体积 |
| 228 | + |
| 229 | +**React 优势**: |
| 230 | +- 成熟的生态系统 |
| 231 | +- 大量社区资源和第三方库 |
| 232 | +- SSR/SSG 完善支持 |
| 233 | +- 稳定的生产就绪状态 |
| 234 | + |
| 235 | +### vs Svelte |
| 236 | + |
| 237 | +| 特性 | Ripple | Svelte | |
| 238 | +|------|--------|--------| |
| 239 | +| 语法风格 | TypeScript/JSX 优先 | HTML 模板优先 | |
| 240 | +| 响应式语法 | `track()` + `@` | `$:` 和 runes | |
| 241 | +| 控制流 | 原生 JS 语句 | 特殊指令 `{#if}` `{#each}` | |
| 242 | +| 类型系统 | TypeScript 原生 | 需要额外配置 | |
| 243 | + |
| 244 | +**Ripple 优势**: |
| 245 | +- TypeScript 优先设计 |
| 246 | +- 使用原生 JS 控制流而非特殊语法 |
| 247 | +- JSX 风格对 React 开发者更友好 |
| 248 | + |
| 249 | +**Svelte 优势**: |
| 250 | +- 更成熟稳定 |
| 251 | +- 完善的 SSR (SvelteKit) |
| 252 | +- 更大的社区和生态 |
| 253 | + |
| 254 | +### vs Solid |
| 255 | + |
| 256 | +| 特性 | Ripple | Solid | |
| 257 | +|------|--------|-------| |
| 258 | +| 组件定义 | `component` 关键字 | 普通函数 | |
| 259 | +| 响应式访问 | `@` 语法 | 函数调用 `count()` | |
| 260 | +| 集合 | 内置 TrackedArray/TrackedObject | 需要 createStore | |
| 261 | +| 模板 | 组件体内语句 | JSX 返回值 | |
| 262 | + |
| 263 | +**Ripple 优势**: |
| 264 | +- 更简洁的响应式访问语法 |
| 265 | +- 内置响应式集合类型 |
| 266 | +- 模板词法作用域特性 |
| 267 | + |
| 268 | +**Solid 优势**: |
| 269 | +- 生产就绪 |
| 270 | +- 标准 JSX 兼容性 |
| 271 | +- 更成熟的生态 |
| 272 | + |
| 273 | +## 快速开始 |
| 274 | + |
| 275 | +### 安装 |
| 276 | + |
| 277 | +```bash |
| 278 | +# 从模板创建新项目 |
| 279 | +npx degit Ripple-TS/ripple/templates/basic my-app |
| 280 | +cd my-app |
| 281 | +npm i && npm run dev |
| 282 | + |
| 283 | +# 或在现有项目中安装 |
| 284 | +npm install ripple |
| 285 | +npm install --save-dev @ripple-ts/vite-plugin |
| 286 | +``` |
| 287 | + |
| 288 | +### 基础示例 |
| 289 | + |
| 290 | +```tsx |
| 291 | +import { track } from 'ripple'; |
| 292 | + |
| 293 | +export component App() { |
| 294 | + let count = track(0); |
| 295 | + |
| 296 | + <div class="container"> |
| 297 | + <h1>{"Welcome to Ripple!"}</h1> |
| 298 | + <p>{"Count: "}{@count}</p> |
| 299 | + <button onClick={() => @count++}>{"Increment"}</button> |
| 300 | + </div> |
| 301 | + |
| 302 | + <style> |
| 303 | + .container { |
| 304 | + text-align: center; |
| 305 | + padding: 2rem; |
| 306 | + } |
| 307 | + button { |
| 308 | + padding: 0.5rem 1rem; |
| 309 | + cursor: pointer; |
| 310 | + } |
| 311 | + </style> |
| 312 | +} |
| 313 | +``` |
| 314 | + |
| 315 | +### 挂载应用 |
| 316 | + |
| 317 | +```typescript |
| 318 | +import { mount } from 'ripple'; |
| 319 | +import App from './App.ripple'; |
| 320 | + |
| 321 | +mount(App, { |
| 322 | + target: document.getElementById('root') |
| 323 | +}); |
| 324 | +``` |
| 325 | + |
| 326 | +## 开发工具支持 |
| 327 | + |
| 328 | +Ripple 提供了完整的开发工具链: |
| 329 | + |
| 330 | +- **VSCode 扩展**: 语法高亮、诊断、IntelliSense |
| 331 | +- **Prettier 插件**: 代码格式化支持 |
| 332 | +- **ESLint 插件**: 代码检查支持 |
| 333 | +- **Vite 插件**: 开发和构建支持 |
| 334 | + |
| 335 | +## 当前限制 |
| 336 | + |
| 337 | +作为早期开发阶段的框架,Ripple 目前存在一些限制: |
| 338 | + |
| 339 | +1. **无 SSR 支持**: 目前仅支持 SPA 模式 |
| 340 | +2. **生态系统不完善**: 第三方库和工具较少 |
| 341 | +3. **可能存在 Bug**: 框架仍在快速迭代中 |
| 342 | +4. **文档不完整**: 部分高级特性文档待完善 |
| 343 | + |
| 344 | +## 总结 |
| 345 | + |
| 346 | +Ripple 是一个充满创新的前端框架,它汇集了 Dominic Gannaway 多年前端框架开发经验的精华。其独特的响应式语法、编译器驱动的架构以及 TypeScript 优先的设计理念,为前端开发提供了一种新的可能性。 |
| 347 | + |
| 348 | +**适合尝试 Ripple 的场景**: |
| 349 | +- 对新技术感兴趣的个人项目 |
| 350 | +- 想要学习现代响应式框架设计原理 |
| 351 | +- 不需要 SSR 的小型应用 |
| 352 | + |
| 353 | +**暂不建议使用的场景**: |
| 354 | +- 生产环境的商业项目 |
| 355 | +- 需要完善 SSR 支持的应用 |
| 356 | +- 依赖大量第三方库的项目 |
| 357 | + |
| 358 | +随着框架的不断成熟,Ripple 有望成为前端开发的一个有力选择。如果你对现代前端框架的设计感兴趣,Ripple 绝对值得关注和尝试。 |
| 359 | + |
| 360 | +## 参考链接 |
| 361 | + |
| 362 | +- [Ripple 官网](https://www.ripplejs.com/) |
| 363 | +- [GitHub 仓库](https://github.com/Ripple-TS/ripple) |
| 364 | +- [官方文档](https://www.ripplejs.com/docs/introduction) |
0 commit comments