前端信号Signal是什么?原子CSS组件是什么?OMI 响应式WebComponents和OmiElements来袭 #871
dntzhang
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
OMI 加入 Signal 之后,现在的 API 设计是我心中最理想的前端框架该有的样子,基于 Tailwind CSS 原子CSS 的 OmiElements 也是我心中理想的前端元素库的形态。基于元素库之上的组件库,和基于组件库的OMI 低代码平台未来也会跟大家见面,敬请期待。我们也对主站和各个子站点进行了全新升级改版。
下面从这些关键字一窥 OMI 的设计思路和一些选择的取舍:
其中 OMI Elements 和 OMI Tutorial 使用上面这些技术里的大部分能力搭建而成:
两个项目可以在 https://github.com/Tencent/omi 找到。
信号 Signal 驱动的响应式编程
在刘慈欣的科幻小说《三体》中,叶文洁在收到来自三体世界的信号后,又收到了另一个警告信号。
叶文洁收到的是信号,信号值是
不要回答!不要回答!不要回答!
。信号是信号,信号值是信号值,信号
不等于信号值
,信号值
等于信号.value
。理解了这个概念,就理解了信号的核心思想。举例说明:上面代码中
count
是信号,在render
方法中读取信号值count.value
,这个时候信号会被收集到当前组件作为信号的依赖,当信号值发生变化时,会自动触发依赖该信号值的组件更新,从而实现了响应式。再强调一次,读取信号的值(也就是.value
),读取信号的组件就会被信号收集为依赖,以后信号值变化,信号依赖的组件就会自动更新。怎么做到的呢?下面介绍一下 OMI Signal 的实现原理。
Proxy:JavaScript 中的强大代理 API
在 JavaScript 中,Proxy 提供了非常强大的元编程能力,它允许开发者在访问、修改对象属性时进行拦截和处理。Proxy 的出现极大地拓展了 JavaScript 编程的可能性,为开发者提供了更多的灵活性和控制力。
Proxy的本质是一个对象,它接受两个参数:目标对象(target)和处理器对象(handler)。目标对象是需要被代理的对象,而处理器对象则包含了一系列用于拦截和处理目标对象操作的方法。
在这个例子中,我们创建了一个简单的Proxy对象,它在访问目标对象的属性时会输出一条日志。当我们通过代理对象访问
foo
属性时,处理器对象的get
方法会被触发,输出日志并返回属性值。Proxy支持多种拦截方法,这些方法可以拦截并处理目标对象的各种操作。以下是一些常用的拦截方法:
get(target, property, receiver)
: 当访问代理对象的属性时触发。set(target, property, value, receiver)
: 当设置代理对象的属性值时触发。has(target, property)
: 当使用in
操作符检查代理对象的属性时触发。deleteProperty(target, property)
: 当删除代理对象的属性时触发。这些拦截方法可以根据需要自由组合,实现各种复杂的逻辑控制。需要注意的是,不是所有的拦截方法都需要实现,未实现的拦截方法将直接访问或操作目标对象。
Proxy 在实际开发中有很多应用场景,数据绑定和响应式更新是最常见的,OMI 框架的信号 Signal 就是基于 Proxy 实现:通过拦截对象属性的访问(收集依赖)和修改操作(更新依赖),实现数据与视图的同步更新。这不是什么新鲜的技术,mobx 很早就使用可观察状态、计算值、依赖跟踪、响应式更新和 action,帮助你更轻松地管理和更新应用状态,它作为独立的状态管理库,OMI 直接内置集成了这些能力,开箱即用。
Web Components
Web Components是一组浏览器原生支持的技术,用于实现可重用、封装良好的自定义 HTML 元素。它为前端开发带来了组件化的革命,使得开发者可以更加高效地构建复杂的 Web 应用。
一些大厂的案例有:
Web Components包括以下三个主要技术:
比如不使用任何框架实现一个自定义元素:
在这个例子中,我们创建了一个自定义元素
my-element
,并在其内部创建了一个 Shadow DOM。当浏览器遇到<my-element>
标签时,会自动创建一个MyElement
实例,并将其附加到文档中。<my-element>
是框架无关的,任何框架都可以使用该元素。其中 OMI 框架使用了其中两种:Custom Elements 和 Shadow DOM,而 HTML Templates 则由编程体验更好 JSX 语法来代替来实现。比如上面的例子,OMI 实现:
JSX 编程体验更好,更短,tag function里模板字符串使用反引号包围,插值需要
$
包围。未来很长一段时间,我们不会使用 HTML Templates或者是 tag function + 真实DOM,而是使用 JSX + 虚拟DOM 语法来实现,因为它的编程体验更好。未来会不会支持 tag function + 真实DOM,同时支持两种语法?,这里我也不敢保证。可以确定的是,除非遇到明显的性能瓶颈,未来很长一段时间都会保持 JSX/TSX + 虚拟DOM。
Tailwind CSS
给变量取名是编程任务中费时的事情之一,比如给一个 HTML 标签想一个精准 class 名称,是非常困难的,千人千面,Tailwind CSS 是解药,帮前端消灭了一门语言,不用考虑命名,不用担心 css 互相影响,不用担心体积膨胀,不用担心项目过大样式文件快速腐化,其收益远大于它的损失,一脚踏进了原子化 CSS 的大门,就再也回不去了。
Tailwind CSS 是用于构建用户界面的 utility-first 的 CSS 框架。致力于通过提供一系列可组合的预设 class 来帮助开发人员快速创建响应式设计。Tailwind CSS 遵循移动优先的设计原则,因此你可以轻松地为不同的设备和屏幕尺寸创建响应式设计。Tailwind CSS 在构建生产版本时,会自动删除未使用的 CSS,从而减小最终文件的大小。
在前年的时候,我就问过 OMI 团队小伙伴,使用原子 CSS 构建组件库可行不可行,最后大家觉得不可行。直到大家看到了 tw-elements 这个项目,原来它是可行的。
在 OMI WebComponents 使用中 Tailwind CSS:
每个组件都携带了 tailwind,那么是不是需要非常大的内存开销?这里就需要提到 constructable stylesheets,可构造的样式表。
Constructable Stylesheets
Constructable Stylesheets 是 Web Components 的黄金搭档。在使用 Shadow DOM 时创建和分布可重复使用的样式的一种方式,既降低了尺寸,还能提高性能。
在介绍 Constructable Stylesheets 之前,我们先来看一下传统的样式应用方式。在传统的 Web 开发中,我们通常会通过以下几种方式来应用样式:
内联样式
直接在 HTML 元素的 style 属性中写样式。这种方式简单直接,但是样式不能复用,而且难以管理。
<style>
标签:在 HTML 文档的<head>
中使用<style>
标签来写样式。这种方式可以应用到整个文档,但是样式仍然不能复用,而且如果样式代码较多,可能会影响 HTML 文档的可读性。外部样式表
通过
<link rel="stylesheet" href="...">
来引入外部的 CSS 文件。这种方式可以复用样式,而且可以将样式代码和 HTML 代码分离,使得代码更易于管理。然而,每个外部样式表都需要一个 HTTP 请求,如果样式表较多,可能会影响页面的加载性能。以上这些方式在大多数情况下都能工作得很好,但是在一些特殊的场景下,它们可能会遇到一些问题。例如,在使用 Web Components 时,我们通常需要在每个组件的 Shadow DOM 中应用样式。由于 Shadow DOM 是隔离的,我们不能直接使用外部样式表或者
<style>
标签,而必须在每个 Shadow DOM 中单独写样式。这不仅使得样式难以复用,而且如果组件数量较多,可能会导致大量的样式代码被重复加载,从而影响性能。为了解决这些问题,Constructable Stylesheets API 提供了一种新的方式来创建、存储和应用样式。这个 API 主要包含以下几个部分:
然后通过 sheet.replace(text) 或者 sheet.replaceSync(text) 来设置样式表的内容。这里的 text 是一个包含 CSS 代码的字符串。
我们可以通过 document.adoptedStyleSheets = [sheet1, sheet2, ...] 或者 shadowRoot.adoptedStyleSheets = [sheet1, sheet2, ...] 来应用样式表。这里的 sheet1, sheet2, ... 是 CSSStyleSheet 对象。
使用 Constructable Stylesheets,我们可以在 JavaScript 中创建和管理样式表,然后在需要的地方动态地应用样式表。这样,我们就可以复用样式,而且只需要加载一次样式代码,从而提高性能。
例如,我们可以创建一个样式表,然后在多个 Shadow DOM 中应用这个样式表,这里举一个不使用 OMI 框架原生使用 Constructable Stylesheets 的例子:
在这个例子中,我们创建了一个样式表 sheet,然后在 my-element 组件的 Shadow DOM 中应用了这个样式表。无论我们创建了多少个 my-element 组件,样式表的代码都只需要加载一次。
SPA & OMI Router & OMI Suspense
SPA(Single Page Application,单页应用)是一种 Web 应用程序开发模式,其特点是在单个 HTML 页面上通过 JavaScript 动态更新和渲染内容,而无需重新加载整个页面。优点包括: 用户体验、响应速度、网络流量减少、前后端分离、离线支持、易于调试和维护。
React Router 使用了下面的方式定义路由,每个 path 指定一个 element,可以支持嵌套路由。
OMI Router 使用了扁平路由设计进行 SPA 搭建,结合 OMI Suspense 和 浏览器原生支持的 Web Components元素自动升级特性,可以逐步显示 Web 区域的内容:
Suspense 是一种用于处理异步加载组件的机制。通过使用Suspense,开发者可以为异步加载的组件提供一个“占位符”,在组件和组件依赖的数据加载完成之前显示给用户。这样一来,用户就不会在等待组件加载时看到空白页面,从而提高用户体验。
一个 path 就对应 一个界面,易于理解和管理。虽然路由是扁平的,但是你在每个路由的 render 函数中使用了嵌套的组件。这是一种常见的模式,可以让你在保持路由扁平的同时,利用组件的嵌套来复用代码和表示层级关系。只是页面有重复的头部和侧边栏的时候需要一些重复代码,后续我们可以考虑支持声明 children 来支持嵌套的形式,但是它也只是扁平结构的语法糖,最后运行的效果是一样的。
OOP & DOP
这里使用 TodoApp举例子说明 OMI 中 Signal 类 和 signal 响应是函数两种响应式编程的方式。
TodoApp 使用响应式函数
在数据驱动编程中,我们将重点放在数据本身和对数据的操作上,而不是数据所在的对象或数据结构。这种编程范式强调的是数据的变化和流动,以及如何响应这些变化。基于响应式函数的 TodoApp 就是一个很好的例子,它使用了响应式编程的概念,当数据(即待办事项列表)发生变化时,UI 会自动更新以反映这些变化。
TodoApp 使用信号类 Signal
在面向对象编程中,我们将重点放在对象上,对象包含了数据和操作数据的方法。这种编程范式强调的是对象之间的交互和协作,以及如何通过对象的封装、继承和多态性来组织和管理代码。基于响应式函数的 TodoApp 也可以使用面向对象的方式来实现,例如,我们可以创建一个 TodoList 类,这个类包含了待办事项列表的数据和操作这些数据的方法,以及一个
update
方法来更新 UI。这里不讨论哪种方式(DOP,OOP)的好坏,使用 omi 两种方式都可以任意选择,也可以通过分层结合一起使用。
我们提倡使用分层的方式来开发前端。使用分层架构的原因很简单,让UI是UI,非UI是非UI。前端框架真是一把双刃剑,可以快速搭建 UI 的同时,很容易让大家把非UI层的,需要认真进行面向对象分析设计的模块被打散夹杂到 UI 层的各种逻辑里面变成一片混沌无序,快速腐化,项目负责人不可替代性越来越强(无人能接,无人能懂啊),强行在 UI 层进行 MVVM/MVC/MVP 分层是错误的。我们希望发挥和享受 OMI 数据驱动的响应式视图、可以快速搭建 UI 的能力的优势,尽量让用户前端框架超越其职责边界,杜绝用户把所有逻辑都塞进 UI 里。
另外我们使用两层架构和三层架构分别写了同一款贪吃蛇游戏,可以发现 OOP & DOP 不冲突,可以通过三层架构一起使用。
源码可以在 http://omijs.org/ 或 https://github.com/Tencent/omi 里找到。
这里我们的建议是:
遵循以上建议,可以有效地提高前端项目的可维护性、可扩展性和可读性。
OMI 低代码
万丈高楼平地起,关于低代码,我们积累了大量的实战经验,每天都能迸发出很多创意和想法,后面会体现在我们的低代码产品当中,敬请期待。
总结
在《股票大作手回忆录》里有个圣杯的概念,许多投资者都试图寻找到它。从客观的角度来看,前端框架也有属于它的圣杯,我们站在巨人的肩膀上,持续寻找前端的圣杯。
本文从非常宏观的角度上,大体地介绍了 OMI 的新特性、和相关技术以及相关官方包,包括 OMI Signal、Web Components、TailwindCSS、OMI Router、OMI Suspense、 Proxy、Constructable Stylesheets、OOP & DOP、JSX、SPA等,希望这些内容能够帮助大家更好地理解和应用 OMI 框架,提高 Web 开发的效率和质量,拥抱趋势。更多详情查看 http://omijs.org/ 或者在 11月18日,杭州 FEDAY 前端大会,我会分享《响应式 WebComponents》,深入地探讨一下。
Beta Was this translation helpful? Give feedback.
All reactions