摘要:因為組件的存在范圍被限制在以內,這就是這種機制目前存在的意義所在。組件都是可以傳遞參數或外部作用域的,利用此機制進行判斷來執行可選行為,這是對用戶友好的舉措。
這一篇還是一個簡單的例子所引發的思考。
你看,如今的框架和庫,無論規模大小功能多少,它們在本質上都朝著“組件化”的思路快速演進著。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 應用的確是很不錯的選擇。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/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...
閱讀 1911·2021-11-09 09:46
閱讀 2489·2019-08-30 15:52
閱讀 2450·2019-08-30 15:47
閱讀 1324·2019-08-29 17:11
閱讀 1749·2019-08-29 15:24
閱讀 3505·2019-08-29 14:02
閱讀 2446·2019-08-29 13:27
閱讀 1204·2019-08-29 12:32