这种全新语法想要取代 JSX

BBetter Stack
Computing/SoftwareInternet Technology

Transcript

00:00:00起初我们有了 JSX,后来又有了 TSX,但多年来我们一直停留在这些技术上。
00:00:04难道它们就不能再改进了吗?好吧,也许 TSRX 可以。
00:00:08它有点像以前的东西,但又有所不同。
00:00:10我们没有 function(函数),取而代之的是 component(组件),文本则是用字符串表示,
00:00:14这里面有普通的 if 语句,而且也没有 return 语句。
00:00:17所以这究竟是什么,为什么要用它,以及你是否应该使用它?让我们一探究竟。
00:00:21[音乐]
00:00:26现在,你们中可能已经有少数人之前见过这样的代码,
00:00:29那是因为它实际上是由 Ripple 的创建者带给我们的。
00:00:31这是一个新的前端框架,Rich 六个月前在这个频道上介绍过,
00:00:35所以订阅我们以跟上这些新技术的步伐。
00:00:38他们所做的是提取了 Ripple 中使用的语法,
00:00:41并让它能够与 React、Preact、Solid、Vue,当然还有 Ripple 一起工作,
00:00:45很多人对此感到非常兴奋。
00:00:47现在,TSRX 将自己描述为一种编写保持易读和协同定位的 UI 组件的方法,
00:00:52这样结构、样式和控制流就能共存,
00:00:55并且其结果对 TypeScript 保持完全的向后兼容。
00:00:58但除非你之前用过 Ripple,否则你可能对这意味着什么还是有点困惑,
00:01:01所以让我们来梳理一下它的特性。
00:01:03首先,你在 TSRX 文件中使用它,这意味着我们需要一个编译步骤,
00:01:07但通过 Vite 插件来设置是非常容易的,
00:01:10而且对于其他框架和运行时,也有其他的选项。
00:01:13至于实际的组件,我们在这里不写 function,而是写 component,
00:01:17这主要只是编译器本身的一个关键字,
00:01:20但它也清楚地表明这将包含一些渲染逻辑。
00:01:24我可能会认为这是体验上的一点微小改进。
00:01:27不过需要注意的一点是,我们这里没有 return 语句,
00:01:30那是因为 TSRX 使用了基于语句(statement-based)的 JSX,
00:01:33所以你不需要返回一个组件树,
00:01:35你只需在任何你想让它渲染的地方写下你的标记(markup)即可。
00:01:37这意味着我们实际上可以在组件顶部的这个卡片上方直接插入另一个段落标签,
00:01:42而它就会在写下的地方直接渲染出来。
00:01:44你仍然可以在组件中使用 return,但它必须是一个空(bare)的 return,
00:01:47它仅用于提前返回(early returning),这样在此之后的 UI 和逻辑就会被跳过。
00:01:51将 TSRX 组件视为非常线性的会对理解有所帮助,
00:01:54因此我们编写源码的顺序就是渲染的顺序,
00:01:57只需从上到下阅读,
00:01:59但我也可以看出,这可能会让人更难快速看出一个组件正在渲染什么,
00:02:03而在像 React 这样的框架中,我们会直接去看 return 语句。
00:02:06基于语句的 JSX 的另一个好处是,我们可以使用更多正常的 JavaScript。
00:02:10例如,条件渲染变得非常简单。
00:02:13如果需要的话,它只是一个带有 else-if 和 else 的 if 语句。
00:02:17在这些条件中,我们只需要将 JSX 作为一个语句放入即可。
00:02:20而在 React 中,同样的逻辑通常会变成嵌套的三元运算符,
00:02:23因为在 JSX 中,每个分支都必须是一个表达式,
00:02:26所以我发现 TSRX 版本有时更容易阅读,
00:02:29特别是当我们有一个更复杂的语句时,
00:02:31但同样地,我也可以看到这可能会增加更多的冗长性,
00:02:35尤其是当你只需要一个简单的条件时。
00:02:37switch 语句也是一样的情况。
00:02:39你只需使用带有各种 case 的普通 JavaScript switch,
00:02:41以及你希望为每个 case 渲染的 JSX。
00:02:44这比你在 React 中处理它的方式要简单一些,
00:02:47在 React 中你需要一个函数来使用相同的模式,
00:02:49所以 TSRX 在这里显得更干净一点,
00:02:51但我个人不太喜欢 TSRX 的地方是在列表渲染中。
00:02:55在这里,我们丢弃了 .map,而是使用 for-of 循环,
00:02:58TSRX 实际上扩展了这个循环,这样我们就可以取出索引,
00:03:01以及带有 key 的稳定标识(identity)。
00:03:03然后当你想要跳过某一项时,你只需简单地使用 continue,
00:03:06所以,它又一次更接近原生的 JavaScript 流程,
00:03:08但正如我所说,对我而言,我已经非常习惯使用 .map、filter 等等,
00:03:12所以我可能会坚持使用那些,
00:03:14而且还值得注意的是,你不能使用任何其他循环类型,
00:03:17比如 for、for-in、while 和 do-while。
00:03:19这只适用于 for-of 循环。
00:03:21现在,继续秉承使用普通 JavaScript 的趋势,
00:03:23我们在 TSRX 中做错误边界(error boundaries)的方式是用一个简单的 try-catch 块。
00:03:27没有什么花哨的,非常显而易见。
00:03:30如果我们需要异步边界(async boundaries),我们也可以使用同样的 try-catch 块,
00:03:33我们只需要添加一个 pending 块,
00:03:35然后把你的加载中(loading)组件写在里面。
00:03:38编译器实际上会处理这段代码,
00:03:40并将它解析为你正在使用的任何框架,
00:03:42所以顶着 React、Preact 和 Solid,这实际上使用的是 lazy,
00:03:45而在 Ripple 中,它就是 Ripple 对应的等价物。
00:03:47不过专门说到 React,
00:03:48我们目前看过的这些特性
00:03:50似乎允许我们打破 React 的一个核心规则,
00:03:53那就是 Hooks 的规则(rule of hooks)。
00:03:54我们现在可以将它们放在条件语句和提前返回之后,
00:03:57甚至放在循环内部。
00:03:58它们都将正常工作。
00:04:00这让我们可以更好地将代码协同定位到实际需要它的地方,
00:04:03而且最终的输出甚至没有打破规则。
00:04:06编译器只是悄悄地把每一个 hook 提升(hoist)到了生成的函数的顶部,
00:04:09所以 React 看到的它们仍然有稳定的顺序,
00:04:11但你却可以把它们写在它们实际属于的地方。
00:04:14现在对我来说,作为一个用了多年 React 的人,
00:04:16这是我很难适应的特性之一,
00:04:18这也是一个我们让编译器
00:04:20在幕后做更多魔法的特性,
00:04:22特别是围绕着某个框架,
00:04:24我想如果我要调试这个,
00:04:26我可能会对哪段代码在什么位置感到有些迷失。
00:04:28不过接下来,我们有词法作用域(lexical scoping),
00:04:30所以每个嵌套元素都在创建它自己的作用域,
00:04:32因此我们能够在这里的三个不同 div 块中声明一个 const label,
00:04:36而它们并不会发生冲突。
00:04:37甚至在函数的顶部也有一个 label,没有任何东西在读取它,
00:04:40而且同样,它也没有冲突。
00:04:41对于每个 if、for、switch 或 try 语句也是一样。
00:04:44每一个都有它自己的作用域,
00:04:46所以我们声明的变量、运行的函数,
00:04:48以及我们推导出的值,它们都不会泄露到其他作用域中。
00:04:51这是另一个专注于协同定位我们代码的特性,
00:04:54同样,它让我们的组件以自上而下的线性方式进行阅读。
00:04:57现在换个话题,抛开 JavaScript,我们来谈谈样式。
00:05:00在 TSRX 中,我们实际上拥有作用域化的样式(scoped styles),
00:05:02所以你只需在组件中放入一个 style 块,
00:05:04我们在其中编写的 CSS 就仅对该组件生效,
00:05:08编译时会向类名(class name)附加一个唯一的哈希值。
00:05:11所以这个 card 组件有一个 card 类,
00:05:13注意这里,它也试图使用那个 card 类,
00:05:16但它没有获得任何 card 的样式,
00:05:17因为他自己没有 style 块。
00:05:19它没有从父组件中获取样式,
00:05:21因为它没有那个唯一的哈希值。
00:05:22但如果你确实想跨组件共享样式,
00:05:24TSRX 有一个 style 关键字,
00:05:26父组件通过这个关键字将样式名称
00:05:29传给一个接受 className 作为 prop 的组件,
00:05:31它会确保生成的那个唯一哈希值也随之传递。
00:05:35所以现在注意,它拥有与父组件相同的样式。
00:05:37你技术上也可以在样式周围使用全局选择器(global selector)
00:05:40来逃离作用域并全局应用这些样式,
00:05:42但我认为那会让事情变得有点混乱,
00:05:44而且你会搞不清你的样式是从哪里来的。
00:05:46就个人而言,我是一个彻头彻尾的 Tailwind 粉丝,
00:05:48所以我可能不会经常使用这个特性,
00:05:50我会坚持使用 Tailwind,
00:05:51但它不管怎么说还是很酷的。
00:05:53接下来,是为那些一直在关注的人准备的特性。
00:05:56在我展示的代码块中,
00:05:57在这些语句中处理文本的方式有一个小小的区别。
00:06:01元素内的裸文本(bare text)必须用双引号包起来。
00:06:04我们不能像在 JSX 中那样直接写进去。
00:06:07不过你仍然可以使用动态值,
00:06:08而在像这样的一行中,
00:06:10它处于两个双引号字符串之间,
00:06:12TSRX 在编译时会简单地将它拼接(concatenate)成一个字符串。
00:06:16你的另一个选择是干脆坚持使用模板字面量(template literal)。
00:06:19它能得到相同的结果。
00:06:20对我来说,这是使用 TSRX 的不爽痛点(paper cuts)之一,
00:06:23因为我已经形成了不用引号写文本的肌肉记忆。
00:06:26不过,另一个基于文本的特性是,
00:06:27TSRX 实际上可以处理包含实际 HTML 标记的字符串,
00:06:31而你有两种方法来渲染它。
00:06:33第一种是直接使用 text 关键字,
00:06:35这将会渲染转义后的文本,
00:06:38所以你可以看到字面上的 HTML 字符串,
00:06:40而且它也免受跨站脚本攻击(XSS)的影响。
00:06:42所以当你想要保证某些内容将是一个字符串,
00:06:45而该字符串的来源有点模糊,
00:06:48以至于你在写这段代码时并不一定知道它的类型,这会非常有用。
00:06:51第二种选择是,如果你想将字符串渲染为 HTML,
00:06:54我们可以直接使用 html 关键字,
00:06:56这会将它解析为真实的 HTML,
00:06:58但这个特性仅在 Ripple 中有效,Vue 仅支持它的一个较小子集。
00:07:02另一个与 React 无关的特性,
00:07:03但可能对那些正在使用 Ripple、
00:07:06Vue 或 Solid 的人来说很有趣,那就是延迟解构(lazy destructuring)。
00:07:08如果你在这些框架中正常解构你的 props,
00:07:10你会在调用时快照(snapshot)每个值,
00:07:12而这会破坏单次访问(per-access)的响应性。
00:07:14所以在 TSRX 中,你只需在 props 前面加上一个 & 符号,
00:07:18虽然它看起来像解构,
00:07:20但每个绑定实际上都会被编译回使用它的属性查找(property lookup)。
00:07:23这样运行时就能分别看到每一次访问,
00:07:25所以 signal 的更新仍然会触发重新渲染,
00:07:28这意味着你既保留了解构的人体工学便利,
00:07:30框架也保留了它的响应性。
00:07:32我将展示的最后一个特性是一个极佳且简单的生活品质改善(quality of life)。
00:07:35你有没有过一个 prop,你传给它的值的名称与该 prop 的名称完全相同,
00:07:40最常见于像 on-change 这样的函数?
00:07:42好吧,使用 TSRX 你实际上可以直接将它写成简写(shorthand),
00:07:45类似于我们对 JavaScript 对象所做的那样。
00:07:47它干净且简单。
00:07:49所以总的来说,TSRX 感觉像是试图将正常的 JavaScript 流重新融合到 JSX 中,
00:07:53同时也加入了一些体验上的改进,
00:07:55而且我非常喜欢它的很多元素。
00:07:57我真心地认为,对我来说它主要的劣势在于它太小众了,而且在这个 AI 编写我们大部分代码的时代来得太晚了,
00:08:01而 AI 擅长编写的代码似乎是 JSX 和 React。
00:08:03尽管如此,我确实把一些演示扔给 Claude,让它使用 TSRX 编写,
00:08:07而它仅仅根据网站的 LLM.txt 就成功地写得很好,
00:08:10但我认为我自己还是会坚持使用普通的 React。
00:08:14另一个缺点是,尤其是对 React 来说,感觉
00:08:17这在顶层添加了更多的编译器魔法,
00:08:19而且它也打破了我花了好几年学习的开发流程,
00:08:21所以它个人而言不适合我,但这并不意味着它不好。
00:08:24我想从 Svelte 转过来的人可能会非常喜欢这个,
00:08:27如果你已经用过 Ripple,你可能已经爱上它了。
00:08:30所以如果你喜欢,就在下方的评论区告诉我,
00:08:33顺便订阅一下,一如既往,我们下期再见。
00:08:35[音乐]
00:08:40[音乐]

Key Takeaway

TSRX 通过基于语句的渲染、局部作用域样式、解除 Hooks 限制以及延迟解构等编译器魔法,将原生 JavaScript 控制流无缝融合到 React、Solid 和 Vue 等主流前端框架中。

Highlights

  • TSRX 是由 Ripple 创建者推出的一种全新前端语法,旨在让结构、样式和控制流在 UI 组件中共存,并对 TypeScript 保持完全向后兼容。

  • TSRX 舍弃了传统的 function 关键字,改用 component 声明组件,并且在组件中不需要写 return 语句即可在编写标记的地方直接渲染 UI。

  • 在使用 TSRX 时,传统的 React Hooks 规则被打破,开发者可以将 Hook 写入条件分支、提前返回逻辑甚至循环内部,编译器会自动将它们提升至生成的函数顶部。

  • TSRX 在列表渲染中抛弃了传统的 .map 方法,转而使用且仅支持 for-of 循环来提取索引和 key,同时支持使用 continue 跳过特定项。

  • 在 TSRX 中,元素内的裸文本必须用双引号包起来(例如 "text"),不能像在普通 JSX 中那样直接书写,编译时会将相邻字符串拼接或使用模板字面量处理。

  • 对于 Ripple、Vue 或 Solid 框架,TSRX 通过在 props 前添加 & 符号实现延迟解构,将解构语法编译回属性查找,从而在保留代码便利性的同时维持响应性。

Timeline

TSRX 的核心定义与组件语法改变

  • TSRX 是一种由 Ripple 创建者开发的全新前端组件编写语法,能够适配 React、Preact、Solid、Vue 和 Ripple。
  • TSRX 组件声明不使用 function 关键字,而是使用编译器专属的 component 关键字。
  • TSRX 文件需要通过编译步骤才能运行,但在项目中使用 Vite 插件可以轻松完成配置。

这种语法旨在提供一种易读且高协同定位的 UI 组件编写方式,使结构、样式和控制流完美共存。它对 TypeScript 保持完全的向后兼容,确保现有项目能够平滑过渡。通过 component 关键字,编译器和开发者都能更直观地识别出哪些代码包含渲染逻辑。

基于语句的 JSX 与控制流重塑

  • TSRX 采用基于语句的 JSX,组件内没有用于返回 UI 树的 return 语句,代码编写的顺序即为 UI 渲染的顺序。
  • 条件渲染和分支逻辑可以直接使用普通的 if-else 和 switch 语句,无需使用 React 中常见的多重嵌套三元运算符。
  • 列表渲染抛弃了传统的 .map 方法,转而使用经过编译器扩展、支持 continue 语句的 for-of 循环。

在基于语句的系统中,标记会被直接写在需要渲染的位置,这使得组件呈现出自上而下的线性阅读特性。如果需要提前终止渲染,开发者只能使用不带返回值的 bare return。此外,在列表渲染中,TSRX 仅支持 for-of 这一种循环类型,不支持普通的 for、while 或 do-while 循环。

try-catch 异步边界与 Hooks 规则的解放

  • TSRX 允许直接使用原生的 try-catch 块来处理错误边界,配合 pending 块即可实现异步加载边界。
  • 开发者可以在条件语句、提前返回逻辑甚至循环内部调用 React Hooks,打破了 React 传统的 Hooks 规则。
  • 每个嵌套的控制流元素都会创建独立的词法作用域,防止声明的变量或函数泄露到其他区域。

在处理异步边界时,编译器会根据所使用的框架将 try-catch 和 pending 块转换为对应的底层实现,例如在 React 和 Solid 中转换为 lazy。针对 React 的 Hook 规则限制,TSRX 编译器会在幕后默默地把所有 Hooks 提升到生成的函数顶部,从而在保证 React 看到稳定调用顺序的同时,让开发者能够自由地将代码写在最符合业务逻辑的地方。

作用域样式、文本处理与高级响应性特性

  • 组件内部编写的 style 块会自动产生作用域限制,编译时会通过向类名附加唯一哈希值来防止样式污染。
  • TSRX 强制要求元素内的裸文本必须使用双引号包裹,或使用模板字面量进行动态拼接。
  • 针对非 React 框架,在 props 前加上 & 符号可以实现延迟解构,从而在享受解构便利的同时保留单次访问的响应性。

对于样式,若需要跨组件共享,可以使用 style 关键字将带有哈希的类名安全地传递给子组件。在文本渲染方面,提供 text 关键字来防止 XSS 攻击,同时提供 html 关键字用于将字符串解析为真实 HTML(此功能目前主要由 Ripple 支持)。此外,TSRX 还支持属性简写,当 prop 的名称与传入的值变量名完全相同时,可以直接合并书写。

TSRX 的实际应用局限与局外局内反思

  • TSRX 的主要劣势在于其生态过于小众,且在 AI 辅助编程时代普及较晚。
  • 过多依赖编译器在幕后进行魔法转换可能会增加代码调试的复杂度和迷失感。
  • 该语法对习惯了 Svelte 或 Ripple 编写方式的开发者具有极高吸引力,但传统 React 开发者可能面临习惯重塑的挑战。

尽管主流 AI 模型(如 Claude)在仅阅读网站 lml.txt 说明文件的前提下,就能非常准确地编写出 TSRX 代码,但它依然面临着打破多年主流开发习惯的阻力。对于深耕 React 生态的开发者来说,隐藏在编译器内部的 Hooks 提升和基于语句的渲染,可能会在排查复杂 bug 时带来额外的认知负担。

Community Posts

View all posts