摘要:因为组件的存在范围被限制在以内,这就是这种机制目前存在的意义所在。组件都是可以传递参数或外部作用域的,利用此机制进行判断来执行可选行为,这是对用户友好的举措。
这一篇还是一个简单的例子所引发的思考。
你看,如今的框架和库,无论规模大小功能多少,它们在本质上都朝着“组件化”的思路快速演进着。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 的地方(所以我见过不少人把它放在 上来扩大这个界限);Ember 默认在 上,当然你可以改;React 也是一样,你总是要把你的第一个 component 渲染到 下面。那么,当你要做的事情超出 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 应用的确是很不错的选择。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/85991.html
React Native Vs. Xamarin Vs. Ionic Vs. Flutter:Which Is Best For Cross-Platform Mobile App Development? While developing Native Mobile Apps, Android apps are written in Java, and iOS ones in Swift and...
摘要:警告版本是很不稳定的,并不推荐使用于要上线的应用。如果你要尝试新的特性,要么是新建一个测试用的,要么是你的应用离正式上线还早并且你和你的团队折腾得起。在此功能正式发布之后应该是不需要这段补丁代码的,目前来说也不会影响使用。 Ruby China 的朋友大概都知道我很喜欢 Ember,然而我用 Ember 的经历其实远比不上 Angular 那么丰富(Ember 业余爱好,Angular...
摘要:好,你用就用吧,各种问题自己也不会看文档问谷歌,成天怨声载道的不得不吐槽一下现在的年轻人。为什么使用有关和的纠结历史可以去谷歌一下,此处不再啰嗦最根本的原因就是对的支持更好,更新和维护也更勤快。 Tips on Ember 2 对我来说是没什么计划性的写作,我只是把它当做是每天工作的总结日志,一个很重要的目的是为团队做一些技术事务的整理,以帮助一些新人快速成长起来。如果有些内容不能满足...
Building virtual reality experiences on the web with React VR Over the past year, virtual reality has made major strides toward becoming the next computing platform. With Oculus Rift, consumer-grade h...
阅读 1903·2021-11-09 09:46
阅读 2486·2019-08-30 15:52
阅读 2445·2019-08-30 15:47
阅读 1319·2019-08-29 17:11
阅读 1745·2019-08-29 15:24
阅读 3500·2019-08-29 14:02
阅读 2441·2019-08-29 13:27
阅读 1198·2019-08-29 12:32