You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OMI 加入 Signal 之后,现在的 API 设计是我心中最理想的前端框架该有的样子,基于 Tailwind CSS 原子CSS 的 OmiElements 也是我心中理想的前端元素库的形态。基于元素库之上的组件库,和基于组件库的OMI 低代码平台未来也会跟大家见面,敬请期待。我们也对主站和各个子站点进行了全新升级改版。
未来很长一段时间,我们不会使用 HTML Templates或者是 tag function + 真实DOM,而是使用 JSX + 虚拟DOM 语法来实现,因为它的编程体验更好。未来会不会支持 tag function + 真实DOM,同时支持两种语法?,这里我也不敢保证。可以确定的是,除非遇到明显的性能瓶颈,未来很长一段时间都会保持 JSX/TSX + 虚拟DOM。
Tailwind CSS
给变量取名是编程任务中费时的事情之一,比如给一个 HTML 标签想一个精准 class 名称,是非常困难的,千人千面,Tailwind CSS 是解药,帮前端消灭了一门语言,不用考虑命名,不用担心 css 互相影响,不用担心体积膨胀,不用担心项目过大样式文件快速腐化,其收益远大于它的损失,一脚踏进了原子化 CSS 的大门,就再也回不去了。
在前年的时候,我就问过 OMI 团队小伙伴,使用原子 CSS 构建组件库可行不可行,最后大家觉得不可行。直到大家看到了 tw-elements 这个项目,原来它是可行的。
在 OMI WebComponents 使用中 Tailwind CSS:
import{tag,Component}from'omi'import{tailwind}from'@/tailwind'
@tag('my-element')exportclassMyElementextendsComponent{staticcss=[tailwind]render(){return(<figureclass="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800"><imgclass="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto"src="/sarah-dayan.jpg"alt=""width="384"height="512"/><divclass="pt-6 md:p-8 text-center md:text-left space-y-4"><blockquote><pclass="text-lg font-medium">
“Tailwind CSS is the only framework that I've seen scale
on large teams. It’s easy to customize, adapts to any design,
and the build size is tiny.”
</p></blockquote><figcaptionclass="font-medium"><divclass="text-sky-500 dark:text-sky-400">
Sarah Dayan
</div><divclass="text-slate-700 dark:text-slate-500">
Staff Engineer, Algolia
</div></figcaption></div></figure>)}}
以上这些方式在大多数情况下都能工作得很好,但是在一些特殊的场景下,它们可能会遇到一些问题。例如,在使用 Web Components 时,我们通常需要在每个组件的 Shadow DOM 中应用样式。由于 Shadow DOM 是隔离的,我们不能直接使用外部样式表或者 <style> 标签,而必须在每个 Shadow DOM 中单独写样式。这不仅使得样式难以复用,而且如果组件数量较多,可能会导致大量的样式代码被重复加载,从而影响性能。
为了解决这些问题,Constructable Stylesheets API 提供了一种新的方式来创建、存储和应用样式。这个 API 主要包含以下几个部分:
CSSStyleSheet 类:这个类代表一个样式表。我们可以通过 new CSSStyleSheet() 来创建一个新的样式表,
然后通过 sheet.replace(text) 或者 sheet.replaceSync(text) 来设置样式表的内容。这里的 text 是一个包含 CSS 代码的字符串。
OMI Router 使用了扁平路由设计进行 SPA 搭建,结合 OMI Suspense 和 浏览器原生支持的 Web Components元素自动升级特性,可以逐步显示 Web 区域的内容:
exportconstroutes=[{path: '/user/:id/profile',render(router: Router){return(<o-suspenseimports={[import('./components/user-info'),]}><divslot="pending">Loading user...</div><divslot="fallback">Failed to load user</div><user-info><o-suspenseimports={[import('./components/user-profile'),]}data={async()=>{returnawaitfetchUserProfile(router?.params.idasstring)}}onDataLoaded={(event: CustomEvent)=>{userProfile.value=event.detail}}><divslot="pending">Loading user profile...</div><divslot="fallback">Failed to load user profile</div><user-profile></user-profile></o-suspense></user-info></o-suspense>)}}]
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
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