新手的對於前端組件化開發的一些疑問?
我是一個前端組件化開發的小白,從接觸前端到現在,寫頁面的基本上都遵從以下順序:
- 把HTML頁面布局寫出來,在寫布局的同時邊寫邊加入CSS
- 頁面的整體布局和樣式出來之後,寫js來實現各種css不能實現的交互效果
- 看項目js文件的複雜程度,有必要的會分模塊寫,目前經常在用AMD寫法,引入require.js
最近看了一些Vue的文檔,入門了一下Vue,趁熱打鐵就用Vue實現了一個很簡單的TodoMVC,其實這是一個很小的項目,可以不用劃分很多組件就能實現,但是為了練習組件化開發,我還是把它劃分了幾個模塊,在實現這個項目的過程中,遇到了一些問題,希望前端大大們能夠幫我解答:
- 組件該如何劃分?
就拿TodoMVC來說,有人劃分的很細,TodoItem、TodoList、Input ,然後TodoItem和TodoList在一塊又組成了ManSection,最後還有一個TodoFooter。而只分一個Input和TodoList以及TodoFooter也能完成項目。所以我想問組件到底該怎麼劃分,在都能實現的前提下,是應該更細的劃分還是都可以,或者有沒有一個約定的大家都喜歡用的做法?
- 組件的方法歸屬?
在實現todo的過程中,有些方法既可以綁在Input組件上,又可以綁在根組件上,舉個例子來說addNewTodo和removeTodo,這兩個方法,可以直接寫在根組件的methods中,又可以寫在Input和TodoList裡面,這又該怎麼取捨?
- 組件的布局和樣式?
拿到一個項目之後,是直接劃分完組件就開始編寫,還是先用HTML把視圖實現一遍,然後再開始寫組件把視圖套進去?
感謝閱讀。
我之所以一直說對custom element持謹慎態度,是因為正如本問題所表達出來的,大多數工程師對組件如何設計劃分是缺乏經驗的。相對來說基於 is 屬性對原有html元素做擴展比較不容易出偏。
組件設計說到底是要做某種抽象。難就難在這種抽象要考慮很多的因素,比如適應UI設計師的建模、工程上的可維護性要求、常見用例和邊緣用例的矛盾、自定義組件和標準html元素的穿插交互、適應將來需求變化發展的可擴展性和可定製性……其中很多點是在開始設計開發時很難確定的,或者隨著業務發展經常會變化。這就是為什麼組件領域比其他技術領域更容易出現抽象泄漏的悲劇。
以這個最簡單的TodoList為例,題主疑惑的一點是組件的粒度,是只要個單一的 TodoList 好,還是要 TodoList、TodoItem、Input ?這其實並沒有一個確定的答案。TodoMVC 項目的目標是通過實現略微複雜一點的 hello world 來展示各種框架或開發方法的大體樣子,本身並沒有其他確定的標準可以輔助你判斷。
所以一方面,前者用起來很簡單,只要用 &
到底哪種好,就必須回歸到具體的場景具體的需求里了。
關於粒度,我們也可以看看 html 原生組件。如 select 組件,看起來 select, optgroup, option 是比較接近後者的。但也有如 video/audio 這樣的組件,其 controls 只有一個屬性控制,並沒有提供任何進一步的定製餘地,因而比較類似前者。你可以考慮下為什麼 html 是這樣設計的?
BTW,如果觀察 html 原生組件的設計,你還會發現向前向後兼容性、無障礙性等因素。一個很有意思的例子是 datalist 的用法(摘自 spec):&
&
這個例子體現出的 html 原生組件的設計方法大家可以體會一下,然後再思考一下自己設計的組件?
先說這些了。最近剛好在帶學生做組件化的實習,談談感想。
首先,組件化從開始寫HTML的時候就應該開始規划了。HTML5新增的一些語義標籤,天然就是組件,導航欄,側邊欄,這些部分我們稍加改造就可以成為獨立的模塊。ul天然就是列表,form明顯是表單,這些原生的內容也很容易被提煉成組件。就像 @賀師俊 說的一樣,對原有HTML元素做擴展式比較不容易偏差的。
我們說到組件化,一般會用components這個詞,多個components組合形成一個page,不同的page用router調度。
components應該是和業務無關的,它只負責渲染給入的數據。比如按鈕是一個組件,可能有一個參數決定了它的尺寸,一個參數決定了它是否可以點擊,但是點擊這個按鈕之後會發生什麼,就不是按鈕這個組件需要知道的事情了。
所以我們的組件都是業務無關,然後把所有的數據放在page中,去調度組件的使用么?這顯然又有哪裡不太對。
問題出在我們在這裡面少了一層結構,components要組成module,然後module和一些簡單components一起形成page。
module就是題主說到的TodoItem和TodoList合在一起的東西。對內來看,它自己持有一些數據和方法,用來決定TodoItem的渲染,對外又是一個簡單的props接受數據。
components和modules,組件和模塊,或者叫做木偶組件和智能組件。
木偶組件是有簡單狀態或者無狀態的,數據幾乎全部依賴於輸入。而智能組件則會擁有一些方法,用來修改持有的數據。智能組件可以持有若干個木偶組件或者其他的智能組件,可以理解為組件樹的非葉子節點,通過自身數據變化,進而操縱子組件的內容。
比如題主說的addNewTodo這件事,可以由input決定,可以有TodoList決定,甚至可以由整個根組件(page)決定。
input應該是一個木偶組件,就像是公司最底層的員工,只能聽命於領導埋頭做事,並沒有決策的權利。所以把方法安排在input上是不合適的。
如果給到根組件呢?讓CEO去負責一個員工的入職,可能在小公司(簡單頁面)里也是可以的,但如果公司特別大,入職這種事情肯定授權給HR來負責了。
所以TodoList就是這個HR,它可以全權負責新增和刪除,既不越權也不屈尊。
組件開發設計和人員的組織架構設計非常像,要分為多少個層級,每個人負責哪些事務,如何把權利和責任落實到合適的層級,這是兩者的共同點。因此,可能把組件擬人之後,會更容易看清應該如何劃分。
至於布局和樣式,我給學生的建議是先寫HTML和CSS,做到大體布局,然後把靜態視圖拆分到各個組件,然後再改動態數據和增加方法,這是一個比較容易實踐的方式。
組件該如何劃分?
個人覺得不要為了組件化而組件化,一定要明白組件化開發解決了什麼問題。比如現在我要開發一個評論列表框:--commentBox----box-header----box-list
----box-footer有多個地方會用到它,所以我們需要將其抽離出來,這就是一個commentBox組件;現在新的頁面來了,我們發現其他地方UI復用了box-list的樣式,交互復用了box-list的操作,所以我們又需要一個box-list模塊了,所以box-list又被我們單獨出來了一個組件,這裡又是一個box-list組件,它可以被commentBox組件所引用。明白這點就清楚哪些需要劃分的細了;組件的方法歸屬?
這個問題同上,如果我把方法綁在了commentBox上,box-list在其他地方被單獨引用的時候就不能用了,所以要根據實際情況來;組件的布局和樣式?
這個東西因人而異吧,怎麼快怎麼來。但是我建議先多想想該組件的復用性講講我一般的步奏吧:
1.先看看原型以及UI,確定好哪些東西會復用,哪些東西抽離出來能降低頁面複雜度;2.將它所需要的視圖,交互等羅列一個列表出來;3.和後台的基友確定數據格式;4.開始對照羅列出來的列表擼頁面;5.重構;以在我實際工作中開發一個價格日曆舉個例子:1.首先我發現多個訂購頁面使用到了這個價格日曆,這個東西肯定是需要抽成組件復用的。2.確定開發列表: (1)需要展示的數據:有日期、價格、單位。(2)需要的數據但是不展示的有:庫存、伺服器當前日期。
(3)操作有:點擊日期選中、且點擊伴隨著展示當前庫存。 (4)考慮擴展性:九月份想打開十月份的日曆所以我需要一個可配置的當前日期、業務會涵蓋酒店,所以需要將酒店的選擇操作留出來;(以上列表舉個例子,並不涵蓋全部)3.和後台的基友確定數據格式,他需要給我傳什麼樣的數據;4.開始對照羅列出來的列表擼頁面,這個沒什麼好說的了,注意下規範(calendar就calendar,別起什麼date);5.重構;重構其實很重要,Martin Fowler在《重構》中說過他腦容量不夠,需要重構代碼,來保證以後再看代碼的時候夠清晰,易讀。雖說是句玩笑話,但是當你去維護之前別人遺留的組件的時候,就能明白前人栽樹後人乘涼的道理了;需要代碼重用的,劃為組件;
需要觸發框架的重渲染優化的,劃為組件,很多框架的優化是以組件為單位的;需求變更時,將需要替換的部分劃為組件,新的實現作為組件插入,這樣容易測試、灰度、回滾等等。這個和設計其他程序沒多大不同,遵循 DRY,不過早優化(設計並實現組件就是種優化),該分的分該合的合,不過度設計等等。在需要動作快時知道可以犧牲什麼(可讀性,可重用性之類),又能保證在穩定後可以逐步重構整理好,不至於變成沒人敢動的東西。
組件不應該有方法,因為組件不應該有狀態,組件應該只是被動地反映狀態,或者回應用戶的交互動作,發出一個狀態變化的消息,在統一的地方處理狀態變化,然後組件再被動地反映出狀態如何變化。例如 AddNewTodo 或者 RemoveTodo,都不應該是組件的一部分,應屬於狀態管理,或者說模型的一部分。Input 組件知道用戶輸入時,應該怎麼通知模型去更新就行了,TodoList 知道需要渲染出哪些Todo就行了。模型和視圖分離是基本原則。模型可以實現成全局的(如redux、mobx、vuex),或者把組件當模型用,如 smart / dumb 組件這種劃分方式。
拿到需求,先設計狀態,想想交互過程中狀態如何變化。設計其實就是寫一個object,寫好了,初始需要用來填充的數據也有了,然後實現。一開始不分組件,在實現的過程里再根據需要來判斷是否抽出組件來,或者增減一些狀態。先實現靜態界面,再實現交互,最後集成進伺服器端來的動態數據,測試沒問題的話,就開發完了。過程和提問里的順序沒多大不同,但交互是從一開始就納入考慮的,而不是補充點綴。組件的目的是為了復用,還有在寫複雜頁面時分割邏輯降低複雜度。
我是邊做靜態頁面邊扣組件,能直接想清楚的就直接扣,比如頭部底部,一時分不太清楚的就邊做邊想,直到想明白再扣,做完靜態頁面還想不明白就只能趴在那再想一會兒了。
組件的劃分我感覺沒有很具體的方法論,大原則就是提高復用性,所謂高內聚低耦合,至於高到哪低到哪,就看自己對項目的理解程度了。組件如何劃分
這個見仁見智, 組件化是為了復用和維護方便
舉個例子
比如有一個list, list里有很多item, 是將他們作為一個組件呢? 還是list一個組件。 item一個組件。
如果結構很簡單,那麼整個作為一個組件就夠用了,相反。 如果item有很多computed, 很多method,那麼可以考慮把item單獨作為一個組件抽離出來, 方便維護 這裡貼個尤大大的例子 vuejs/vue-hackernews-2.0 , 你可以參考下這個例子的組件化粒度
回到你這個例子, 如果不考慮練習因素, 我就一鍋端了 , 就一個todoList有啥好分的 ? ???
- 組件的方法歸屬?
原則上就近, 不然還得一層層去找, 這閱讀體驗 ... 想想都可怕
這個屬於個人習慣問題, 怎麼高興怎麼來
- 組件的布局和樣式?
任何複製粘貼三次以上的都可以考慮組件化!如果只有一兩處,還是考慮複製貼貼吧!
有一個很實用的標準:能夠進行單元測試。
如果一個組件,你發現無法給它寫出單元測試,或者單元測試很難寫,或者寫出來覺得很彆扭,總覺得哪裡不對,說明這個組件還有繼續被拆分的可能。針對題主的「組件該如何劃分」的提問作答:
TodoMVC的場景並不能很好的展示組件化背後的考量。組件化的設計是來自實戰的經驗積累。不過有兩個核心的點:
1. 化繁為簡。也就是通俗意義上的「抽象」的目的。2. 復用。把看似複雜的todo列表,簡化為一個組件負責列表框,一個組件負責框裡面的內容,就是化繁為簡的考慮。
TodoItem和TodoList是否要融合的問題,可以想像現實世界中超市的貨架,貨架本身可以增加或減少層數,但是貨架上可以放的東西,一般來說是千變萬化的,兩者是解耦的。TodoList就是貨架,TodoItem就是貨架上的商品。所以答案不言自明,兩者分開會有更好的擴展性和適配性。舉個簡單的場景:有些TodoItem是已完成的,我希望加上一個歸檔按鈕,hover時出來一個列表,把這個已完成的任務歸檔到我列表中;如果是未完成的,我希望用它來設置我的日程表,hover的時候浮出來一個日程表,我可以在日程表上操作將這個todo加進去。這時候TodoItem本身的功能開始分化,甚至需要定製化,如果做到了TodoList裡面,整個TodoList的代碼將及其複雜,完成功能後沒人想去再看它一眼。文中說的Input這種小組件,本質上還是復用。它本身對DOM結構不敏感,可以放置到列表頭部,也可以塞進列表內部。這種高內聚低耦合的,復用的場景會很多,單獨拎出來形成組件是沒有異議的。反過來,基於「復用」這個要求,也能延展出很多的約束條件,決定了你該如何去實現你的組件,相關的問題自然也就有了答案。既然提到了TodoList,那我就來強答一下吧。
提到的RequireJS,從不會到會,兩個小時吧。然後很輕鬆就做了一個TodoList,參考連接。https://zhuanlan.zhihu.com/p/22011216關於組件化的思考,好巧,前幾個星期剛好寫了篇辣雞專欄。點擊鏈接後應該能找到。就不貼了。歡迎交流。
我是分割線
(說真的,新手就應該好好寫程序,別想太多。先要為伊消得人憔悴,才能發現,那人卻在燈火闌珊處)如果項目不大,完全沒必要組件化,大家別錘我,聽我說完~~
對於新手來說,如果組件封裝的不夠優雅,倒是容易給自己埋雷,所以建議先把某個大塊寫出來,隨著項目進展,根據情況對粘貼的代碼進行組件化抽出即可。對於我這樣經驗不足的開發, 我一直在尋找一種可以應用的組件劃分的方式, 稍微彌補一下 『因為經驗不足導致的組件劃分不合理』的問題。
組件的劃分
1 組件的劃分問題, 有些是剛開始就可以意識到需要抽取成一個組件, 通常見於多個頁面的重複元素.
但是有時候有些是在做了之後才發現, 某個地方抽取成組件的話, 可能會好點.2 又有一點, 在開發完成之後會發現, 面對多層嵌套的html結構, 比如 7,8 層的話, 通常都不太願意去修改.
基於上面的兩點, 我的處理方式是:
控制一下 html 的嵌套層數, 一般是超過 3 層, 就把 3 層以下抽取出來單獨作為一個組件存在. 比如 .container (1) .header (2) .content (3) .list .xxx .yyy .zzz這裡就會把 list 抽取出來作為單獨的一個組件.這樣做的好處a 在你做完之後, 發現某個地方可以抽取出來成為一個組件的時候, 和你剛開始直接抽取出來, 到最後發現有些過度組件化,想要幹掉這個組件. 二者的工作量是有差距的, 個人認為後者的工作量更少一點, 並且簡單一些.b 因為控制住了 嵌套層數, 再配合 vue的 scoped, 再加上 BEM, 基本上不會出現 css 命名衝突的問題了.c 修改和維護的時候, 看起來也舒服一點.組件的行為
組件應該擁有自己的行為. 所以如果一個方法可以放到組件裡面的話, 就放到組件里. 假設某個組件A 在 5 個 頁面裡面被使用到, 如果放到 page 裡面, 那麼會重複 5次(或者抽取相關方法 import 一下) 但是總是沒有直接放到組件裡面方便. 另外如果某個行為出現問題了, 可以直接去組件的代碼裡面尋找問題, 而不是它的調用者.但是有個問題需要注意: 子組件不應該包含 數據轉換的處理, 比如根據 index 從 map 裡面拿到某個欄位再展示出來。 這個應該由父組件直接傳遞需要的數據過來.這樣做的原因是為了防止 子組件的邏輯複雜化, 它應該始終符合我們的期待, 而不是自己做了一些特殊情況的處理.組件的布局和樣式
組件的布局, 樣式應該完全和其所處的 page 無關. 不論它在那個頁面, 它都應該呈現出相同的樣子. 那麼如果想要在 pageA, pageB 裡面 呈現一些細微上面的不同呢? 我想到的做法是, 預先定義好 .in-pageA,.in-pageB 兩個 class, 在不同的頁面裡面使得組件應用 不同的class 即可.一點淺薄認知...推薦閱讀:
※女生做web前端會很辛苦嗎?
※react報錯Each child in an array or iterator should。。?
※為什麼前端工程師很少用 Visual Studio (Windows)?
※怎麼提高自己的前端技術?
TAG:前端開發 | JavaScript | 前端工程師 | Vuejs | 前端組件 |