谈一谈组件化

前言 今天前端生态里面,ReactAngularVue三分天下。虽然这三个框架的定位各有不同,但是它们有一个核心的共同点,那就是提供了组件化的能力。W3C也有Web Component的相关草案,也是为了提供组件化能力。今天我们就来聊聊组件化是什么,以及它为什么这么重要。
正文 其实组件化思想是一种前端技术非常自然的延伸,如果你使用过HTML,相信你一定有过“我要是能定义一个标签就好了”这样的想法。HTML虽然提供了一百多个标签,但是它们都只能实现一些非常初级的功能。
HTML本身的目标,是标准化的语义,既然是标准化,跟自定义标签名就有一定的冲突。所以从前端最早出现的2005年,到现在2022年,我们一直没有等到自定义标签这个功能,至今仍然是Draft状态。
但是,前端组件化的需求一直都存在,历史长流中工程师们提出过很多组件化的解决方案。
ExtJS
Ext JS是一个流行的JavaScript框架,它为使用跨浏览器功能构建Web应用程序提供了丰富的UI。我们来看看它的组件定义:

MainPanel = function() { this.preview = new Ext.Panel({ id: "preview", region: "south" // ... }); MainPanel.superclass.constructor.call(this, { id: "main-tabs", activeTab: 0, region: "center" // ... }); this.gsm = this.grid.getSelectionModel(); this.gsm.on( "rowselect", function(sm, index, record) { // ... }, this, { buffer: 250 } ); this.grid.store.on("beforeload", this.preview.clear, this.preview); this.grid.store.on("load", this.gsm.selectFirstRow, this.gsm); this.grid.on("rowdbclick", this.openTab, this); }; Ext.extend(MainPanel, Ext.TabPanel, { loadFeed: function(feed) { // ... }, // ... movePreview: function(m, pressed) { // ... } });

你可以看到ExtJS将组件设计成一个函数容器,接受组件配置参数optionsappend到指定DOM上。这是一个完全使用JS来实现组件的体系,它定义了严格的继承关系,以及初始化、渲染、销毁的生命周期,这样的方案很好地支撑了ExtJS的前端架构。
https://www.w3cschool.cn/extj...
HTML Component
搞前端时间比较长的同学都会知道一个东西,那就是HTCHTML Components),这个东西名字很现在流行的Web Components很像,但却是不同的两个东西,它们的思路有很多相似点,但是前者已是昨日黄花,后者方兴未艾,是什么造成了它们的这种差距呢?
因为主流浏览器里面只有IE支持过HTC,所以很多人潜意识都认为它不标准,但其实它也是有标准文档的,而且到现在还有链接,注意它的时间!
http://www.w3.org/TR/NOTE-HTM...
MSDN onlineHTC的定义仅如下几句:
HTML Components (HTCs) provide a mechanism to implement components in script as Dynamic HTML (DHTML) behaviors. Saved with an .htc extension, an HTC is an HTML file that contains script and a set of HTC-specific elements that define the component.
(HTC是由HTML标记、特殊标记和脚本组成的定义了DHTML特性的组件.)
作为组件,它也有属性、方法、事件,下面简要说明其定义方式:
  • :定义HTC,这个标签是其他定义的父元素。
  • : 定义HTC的属性,里面三个定义分别代表属性名、读取属性、设置属性时HTC所调用的方法。
  • :定义HTC的方法,NAME定义了方法名。
  • :定义了HTC的事件,NAME定义了事件名,ID是个可选属性,在HTC中唯一标识这个事件。
  • :定义了浏览器传给HTC事件的相应方法,其中EVENT是浏览器传入的事件,ONEVENT是处理事件的方法。
我们来看看它主要能做什么呢?
它可以以两种方式被引入到HTML页面中,一种是作为“行为”被附加到元素,使用CSS引入,一种是作为“组件”,扩展HTML的标签体系。
行为为脚本封装和代码重用提供了一种手段 通过行为,可以轻松地将交互效果添加为可跨多个页面重用的封装组件。例如,考虑在 Internet Explorer 4.0 中实现onmouseover highlight的效果,通过使用 CSS 规则,以及动态更改样式的能力,很容易在页面上实现这种效果。在 Internet Explorer 4.0 中,实现在列表元素li上实现 onmouseover 高亮可以使用onmouseoveronmouseout事件动态更改li元素样式:
.HILITE { color:red; letter-spacing:2; }
  • HTML Authoring

Internet Explorer 5 开始,可以通过 DHTML 行为来实现此效果。当将DHTML行为应用于li元素时,此行为扩展了列表项的默认行为,在用户将鼠标移到其上时更改其颜色。
下面的示例以 HTML 组件 (HTC) 文件的形式实现一个行为,该文件包含在hilite.htc文件中,以实现鼠标悬停高亮效果。使用 CSS 行为属性将行为应用到元素li 上。上述代码在 Internet Explorer 5 及更高版本中可能如下所示:
// hilite.htc//元素定义了浏览器传给HTC事件的相应方法,其中EVENT是浏览器传入的事件,ONEVENT是处理事件的方法

通过CSS behavior属性将DHTML行为附加到页面上的元素
LI {behavior:url(hilite.htc)}
  • HTML Authoring

HTC自定义标记 我们经常看到某些网页上有这样的效果:用户点击一个按钮,文本显示,再次点击这个按钮,文本消失,但浏览器并不刷新。下面我就用HTC来实现这个简单效果。编程思路是这样的:用HTC模拟一个开关,它有”on””off”两种状态(可读/写属性status);用户可以设置这两种状态下开关所显示的文本(设置属性 turnOffTextturnOnText);用户点击开关时,开关状态被反置,并触发一个事件(onStatusChanged)通知用户,用户可以自己写代码来响应这个事件;该HTC还定义了一个方法(reverseStatus),用来反置开关的状态。下面是这个HTC的代码:

html页面引入自定义标记
文本内容......

这项技术提供了事件绑定和属性、方法定义,以及一些生命周期相关的事件,应该说已经是一个比较完整的组件化方案了。但是我们可以看到后来的结果,它没有能够进入标准,默默地消失了。用我们今天的角度来看,它可以说是生不逢时。
如何定义一个组件
ExtJS基于面向对象的思想,将组件设计成函数容器,拥有严格的继承关系和组件生命周期钩子。HTC利用IE浏览器内置的一种脚本封装机制,将行为从文档结构中分离,通过类似样式或者自定义标识的方式为HTML页面引入高级的自定义行为(behavior)。从历史上组件化的尝试来看,我们应该如何来定义一个组件呢?
首先应该清楚组件化的设想为了解决什么问题?不言而喻,组件化最直接的目的就是复用,提高开发效率,作为一个组件应该满足下面几个条件:
  • 封装:组件屏蔽了内部的细节,组件的使用者可以只关心组件的属性、事件和方法。
  • 解耦:组件本身隔离了变化,组件开发者和业务开发者可以根据组件的约定各自独立开发和测试。
  • 复用:组件将会作为一种复用单元,被用在多处。
  • 抽象:组件通过属性和事件、方法等基础设施,提供了一种描述UI的统一模式,降低了使用者学习的心智成本。
接下来我们深入具体的技术细节,看看组件化的基本思路。首先,最基础的语义化标签就能看作成一个个组件,通过DOM API可以直接挂载到对应的元素上:
var element = document.createElement('div') document.getElementById('container').appendChild(element)

但是实际上我们的组件不可能这么简单,涵盖场景比较多的组件都比较复杂,工程师想到将组件定义为原生JS中的Function或者Class容器,形如:
function MyComponent(){ this.prop1; this.method1; …… }

不过,要想挂载又成了难题,普通的JS对象没法被用于appendChild,所以前端工程师就有了两种思路,第一种是反过来,设计一个appendTo方法,让组件把自己挂到DOM树上去。
function MyComponent(){ this.root = document.createElement("div"); this.appendTo = function(node){ node.appendChild(this._root) } }

第二种比较有意思,是让组件直接返回一个DOM元素,把方法和自定义属性挂到这个元素上:
function MyComponent(){ var _root = document.createElement("div"); root.prop1 // = ... root.method1 = function(){ /*....*/ } return root; }document.getElementById("container").appendChild(new MyComponent());

下面我们根据上面思想来设计一个轮播组件,能够自动播放图片
谈一谈组件化
文章图片

Document.carousel, .carousel > img { width: 500px; height: 300px; }.carousel { display: flex; overflow: hidden; }.carousel > img { transition: transform ease 0.5s; }

效果:

未完待续!!!
参考:
HTC(HTML Component) 入门
【谈一谈组件化】HTML Component

    推荐阅读