这一篇照样一个简朴的例子所激发的思索。
你看,如今的框架和库,不论范围大小功用若干,它们在本质上都朝着“组件化”的思绪疾速演进着。Angular 有 directives,Angular 2应当也照样这个叫法;Ember 从 View 过渡到了 Component,而且接下来的迭代会朝向 WebComponent 的规范来想象生命周期及其 API;React 本身就是一个组件化的范式;另有 Polymer,那就是 Google 为 WebComponent 搞得一套 polyfills……人人都这么玩。
我们都没法完全准确的展望 WebComponent 能让 Web 进化到什么水平,然则如今已有了这么多可用的东西了,人人自然是摩拳擦掌的搞起。我也没有破例,在之前运用 Angular 的时刻就在全力做组件的笼统,把 directives 那一套算是玩儿转了——那个时刻并没有意想到一件事变,直到近来返回 Ember 今后才最先有所体味。几天前我在 Ember Community 提了个题目来议论此事,虽然也获得许多发起却照样迷迷糊糊的;厥后又和 @darkbaby123 询问了一番,谢谢他给了我许多启示。但是一直没有想到一个确实的运用场景来考证一番。
说了半天预计你们都看糊涂了:究竟什么事变啊?
Components 用来封装可重用的 HTML+CSS+JavaScript 片断,这很好,但是当下的框架们都要处置惩罚事宜托付的题目,这就意味着框架会给你一个范围来开辟你的运用,这个范围的边境就是框架用于事宜托付的临界线,出了这条线就是浏览器本身的事宜处置惩罚机制在作用了。那末,当你不能不“迈过”这条线的时刻,框架(以及它供应的组件化机制)该如何协助你呢?
举例来讲,Angular 的边境在你声明 ng-app
的处所(所以我见过不少人把它放在 <html></html>
上来扩展这个界线);Ember 默许在 <body></body>
上,固然你能够改;React 也是一样,你老是要把你的第一个 component 衬着到 <body></body>
下面。那末,当你要做的事变超越 body
之外,它们应当如何处置惩罚呢?
关于像 jQuery 如许以 DOM 为中间(固然另有 BOM)的东西来讲,这个题目很简朴——以 DOM 为中间就意味着浏览器有什么你就用什么,不过就是它原生的 API 不好用或不够用,你拿过来用 jQuery 封装一下就好了,换汤不换药。所以当你要操纵 body
之外的东西,原生的东西随意你用,比方 document
(DOM),比方 window
(BOM) 等等,你唯一要做的就是表面套一个 $()
壳子。
在我们的大脑模子里已习惯了把 HTML 和 DOM 视为一体,但除此之外 DOM 和 BOM 还供应了雄厚的接口来处置惩罚许多分外的事变,每个框架或库都邑多若干少供应这些分外接口的封装,比方 Angular 里的 $cookie
,$location
,以至痛快完全的 $document
和 $window
,但是当这些东西和 component 关联在一起的时刻,事变会变得玄妙起来:什么能够做不能够做?什么时候/那边来做?这些题目的界线变得摇摆不定。
Angular 有 DI(依靠注入)的机制,在 directives 的层面上,它奇妙的想象了一个 Attribute Level 的 directive 定义,经由历程 DI 你能够把超越 body
之外的操纵经由历程 HTML 的属性绑定给其他的 Tag Level 的 directives。由于组件的存在范围被限定在 body
以内,这就是这类机制(现在)存在的意义地点。我们还不晓得当 WebComponents 尘嚣落定之时会给出我们如何的答案,固然届时 JavaScript 已有了 modules,所以全局污染的题目已不复存在,如今唯一不明朗的就是如何与组件的生命周期关联起来。
让我们来看一个例子。如今有许多运用都有如许的想象:Header 与 Main Content 没有显著的界线,看起来像一个团体。但假如 Main Content 的内容超越了浏览器一屏的高度,那末当用户向下转动的时刻,Header 会“浮”起来(经由历程下放的暗影)并牢固在窗口顶部,很不错的视觉结果。
题目就在于监听用户转动事宜的行动应当是发作在 BOM 范围内的,假如你的运用是基于以组件为中间的头脑开辟的,这个行动究竟应当在那里做?
这个题目实在会有许多变数,比方说你能够想象这个行动和任何详细的组件无关,而是在运用程序初始化的时刻直接实行。很好,然则有两个题目:
牢固 Header 并为它增加暗影是须要 Header 已存在于 DOM 当中的,通常在运用程序初始化的时刻这个前提还没有杀青
这个行动并非发作在全局范围以内的,比方说某个路由进入今后或某种组件衬着今后才发作
以上恣意一点都能够反对初始化实行这个计划,假如你斟酌久远和全面一些的话就必须另寻前途。
好,我不空话了,先把近来用 Ember 完成的这个例子代码写出来,末了我再说一点对此的主意吧。
第一步,把 Header 笼统为组件
这个很简朴,直接 ember generate component app-header
就好了,代码略过。
第二步,在组件衬着今后实行监听用户向下转动的事宜并为组件增加 class,这个 class 完成了暗影等结果。
const SCROLL_THRESHOLD = 50 // header' height is 50px
export default Ember.Component.extend({
classNameBindings: ['sticky'],
didInsertElement() {
window.addEventListener('scroll', () => {
if (window.scrollY >= SCROLL_THRESHOLD) {
this.set('sticky', true)
} else {
this.set('sticky', false)
}
}
}
})
第三步,当组件烧毁后,注销监听回调
这就能够发作在 body
之外的操纵能和组件的生命周期紧密联系在一起。这一点很主要,不论你用 Ember 照样 Angular/React,肯定要注意组件的生命周期,特别是组件烧毁时这些框架都邑供应对应的 hook,要注意清算“渣滓”,移除绑定,开释内存等等,防止内存走漏。
const SCROLL_THRESHOLD = 50 // header' height is 50px
function _stickHeaderHandler() {
if (window.scrollY >= SCROLL_THRESHOLD) {
this.set('sticky', true)
} else {
this.set('sticky', false)
}
}
export default Ember.Component.extend({
classNameBindings: ['sticky'],
didInsertElement() {
window.addEventListener('scroll', _stickHeaderHandler.bind(this)) // remember to bind!!!
},
willDestroyElement() {
window.removeEventListener('scroll', _stickHeaderHandler)
}
})
DONE
我们还能够如何革新它呢?题目有二:
组件不应当固化特别的行动,假如这个组件是跨运用同享的(比方你宣布成 Addon),那末其他运用多是不须要置顶的运用者希冀的是以下的可选项:
{{app-header stickyOnScroll=50}}
监听转动那一套行动假如不是组件特有的(这就派出了发步成 Addon 的前提)而是运用内同享的,则应当想办法笼统出去——监听转动这个事变很典范
关于题目一,答案已展现在那里了。组件都是能够通报参数或外部作用域的,应用此机制举行推断来实行可选行动,这是对用户友爱的行动。
关于题目二,在 Ember 里你至少有三个选项:
笼统成 Mixin。这个很直观,瑕玷是 Mixin 供应的属性不是 default value,它不能由你主动去掩盖,不够天真;
定义成新的 Component。须要继续的其他组件能够 extend 它,处理 Mixin 不够天真的题目,范围是只能给组件用——不过关于处置惩罚浏览器事宜和操纵 DOM/BOM 已够用了;
笼统成 Service。这个等价于 Angular 的 DI,能够由你本身定义雄厚的接口来设置和挪用,最天真,合适封装须要的外部接口等等。
关于 Service,详细的代码先 hold,今后我会特地讲 Service 在 Ember 里的用法。本日这个例子不合适笼统 Service,缘由就是上面的第二点。
当我在几天前对此还很疑心时,我一度以为像 Angular 的 Attribute Level Directives 才是处置惩罚此类题目的最好计划,但是 WebComponent 并没有 Attribute Level Component 这类想象,这也是我疑心的最初缘由。如今想想,Attribute Level Directives 即是疏忽组件的生命周期(固然它有本身的生命周期,然则和要附着的目的组件无关,你得治理两份),它把可选行动附着于目的组件的历程等同于你建立一个新的特别的 Service(特别的地方就在于它能够放在模版里),然后应用这个 Service 去写完成代码而且还能够再 DI 其他的 Services,以此来完成可选性和可复用性。这类想象乍看讨巧但也有许多瑕玷,比方说多个 directives 共存的时刻要斟酌优先级和行动掩盖的题目,比方说和将来的 WebComponents 不兼容革新起来很省事,等等。
如今我们看到,React 一最先做得就很不错(后起之秀自创了许多先辈们的经验教训),不过它只是一个衬着引擎,做大型运用还须要你在团体架构高低工夫;Ember 的架构很完全,之前的题目许多但如今都在逐一完美,想象思绪没有什么毛病,拿来做 UI 交互庞杂的 web 运用的确是很不错的挑选。