這里我們一起來(lái)學(xué)習(xí)前端組件化的知識(shí),而組件化在前端架構(gòu)里面是最重要的一個(gè)部分。
講到前端架構(gòu),其實(shí)前端架構(gòu)中最熱門的就有兩個(gè)話題,一個(gè)就是組件化,另一個(gè)就是架構(gòu)模式。組件化的概念是從開始研究如何擴(kuò)展 HTML 標(biāo)簽開始的,最后延伸出來(lái)的一套前端架構(gòu)體系。而它最重要的作用就是提高前端代碼的復(fù)用性。
架構(gòu)模式就是大家特別熟悉的 MVC
, MVVM
等設(shè)計(jì)模式,這個(gè)話題主要關(guān)心的就是前端跟數(shù)據(jù)邏輯層之間的交互。
所以說(shuō),前端架構(gòu)當(dāng)中,組件化可以說(shuō)是重中之重。在實(shí)際工程當(dāng)中,其實(shí)組件化往往會(huì)比架構(gòu)模式要更重要一些。因?yàn)榻M件化直接決定了一個(gè)前端團(tuán)隊(duì)代碼的復(fù)用率,而一個(gè)好的組件化體系是可以幫助一個(gè)前端團(tuán)隊(duì)提升他們代碼的復(fù)用率,從而也提升了團(tuán)隊(duì)的整體效率。
因?yàn)閺?fù)用率提高了,大家重復(fù)編寫的代碼量就會(huì)降低,效率就會(huì)提高,從而團(tuán)隊(duì)中的成員的心理和心智負(fù)擔(dān)就會(huì)少很多。
所以學(xué)習(xí)組件化可以是說(shuō)是非常重要的
這里我們先從了解什么是組件化和一個(gè)組件的基本組成部分開始。
組件的基本概念
組件都會(huì)區(qū)分為模塊和對(duì)象,組件是與 UI 強(qiáng)相關(guān)的,所以某種意義上我們可以認(rèn)為組件是特殊的模塊或者是特殊的對(duì)象。
組件化既是
對(duì)象
也是模塊
組件化的特點(diǎn)是可以使用樹形結(jié)構(gòu)來(lái)進(jìn)行組合,并且有一定的模版化的配置能力。這個(gè)就是我們組件的一個(gè)基本概念。
對(duì)象與組件的區(qū)別
首先我們來(lái)看對(duì)象,它有三大要素:
- 屬性 —— Properties
- 方法 —— Methods
- 繼承關(guān)系 —— Inherit
在 JavaScript 中的普通對(duì)象可以用它的屬性,方法和繼承關(guān)系來(lái)描述。而這里面的繼承,在 JavaScript 中是使用原型繼承的。
這里說(shuō)的 “普通對(duì)象” 不包含復(fù)雜的函數(shù)對(duì)象或者是其他的特殊對(duì)象,而在 JavaScript 當(dāng)中,屬性和方法是一體的。
相對(duì)比組件,組件里面包含的語(yǔ)義要素會(huì)更豐富一點(diǎn),組件中的要素有:
- 屬性 —— Properties
- 方法 —— Methods
- 繼承 —— Inherit
- 特性 —— Attribute
- 配置與狀態(tài) —— Config & State
- 事件 —— Event
- 生命周期 —— Lifecycle
- 子組件 —— Children
Properties
和 Attribute
在英語(yǔ)的含義中是有很大的區(qū)別的,但是往往都會(huì)翻譯成 “屬性”。 如果遇到兩個(gè)單詞都出現(xiàn)的時(shí)候,就會(huì)把 Attribute
翻譯為 “特性”,把 Properties
翻譯成 “屬性”。這兩個(gè)要素要怎么區(qū)分呢?這里在文章的后面會(huì)和大家一起詳細(xì)了解。
接下來(lái)就是組件的 Config
,它就是對(duì)組件的一種配置。我們經(jīng)常會(huì)在一個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象的時(shí)候用到 Config
,我們傳入這個(gè)構(gòu)造函數(shù)的參數(shù)就叫 “Config
(配置 )”。
同時(shí)組件也會(huì)有 state
(狀態(tài))。當(dāng)用戶去操作或者是一些方法被調(diào)用的時(shí)候,一個(gè) state
就會(huì)發(fā)生變化。這種就是組件的狀態(tài),是會(huì)隨著一些行為而改變的。而 state
和 properties
、attributes
、config
都有可能是相識(shí)或者相同的。
event
就是 “事件” 的意識(shí),而一個(gè)事件是組件往外傳遞的。我們的組件主要是用來(lái)描述 UI 這樣的東西,基本上它都會(huì)有這種事件來(lái)實(shí)現(xiàn)它的某種類型的交互。
每一個(gè)組件都會(huì)有生命周期 lifecycle
,這個(gè)一會(huì)兒在文章的后面會(huì)詳細(xì)的展開學(xué)習(xí)。
組件的 children
是非常重要的一部分,children
也是組件當(dāng)中一個(gè)必要的條件,因?yàn)闆](méi)有 children
組件就不可能形成樹形結(jié)構(gòu),那么描述界面的能力就會(huì)差很多。
之前有一些比較流行的拖拽系統(tǒng),我們可以把一些寫好的 UI 組件拖到頁(yè)面上,從而建立我們的系統(tǒng)界面。但是后面發(fā)現(xiàn)除了可以拖拽在某些區(qū)域之外,還需要一些自動(dòng)排序,組件嵌套組件的功能需求。這個(gè)時(shí)候組件與組件之間沒(méi)有樹形結(jié)構(gòu)就不好使了。
最后組件在對(duì)象的基礎(chǔ)上添加了很多語(yǔ)義相關(guān)的概念,也是這樣使得組件變成了一種非常適合描述 UI 的概念。
組件 Component
我們用一張圖來(lái)更深入的了解組件。
組件最直接產(chǎn)生變化的來(lái)源就是用戶的輸入和操作,比如說(shuō)當(dāng)一個(gè)用戶在我們的選擇框組件中選中了一個(gè)選項(xiàng)時(shí),這個(gè)時(shí)候我們的狀態(tài) state
,甚至是我們的子組件 children
都會(huì)發(fā)生變化。
圖中右邊的這幾種情況就是組件的開發(fā)者與組件的關(guān)系。其中一種就是開發(fā)者使用了組件的標(biāo)記代碼 Markup Code
,來(lái)對(duì)組件產(chǎn)生影響。其實(shí),也就是開發(fā)者通過(guò)組件特性 Attribute
來(lái)更改組件的一些特征或者是特性。
Attribute 是一種聲明型的語(yǔ)言,也是
標(biāo)記型代碼 Markup Code
。而 Markup Code 也不一定是我們的 HTML 這種 XML 類的語(yǔ)言。在標(biāo)記語(yǔ)言的大生態(tài)中,其實(shí)有非常多的語(yǔ)言可以用來(lái)描述一個(gè)界面的結(jié)構(gòu)。但是最主流的就是基于 XML 體系的。在我們 Web 領(lǐng)域里面最常見的就是 XML 。而 JSX 也可以理解為一種嵌入在編程語(yǔ)言里面的 XML 結(jié)構(gòu)。
開發(fā)者除了可以用 Attribute
,也可以用 Property 來(lái)影響組件。這個(gè)組件本身是有 Property
(屬性) 的,當(dāng)開發(fā)者去修改一個(gè)組件的屬性時(shí),這個(gè)組件就會(huì)發(fā)生變化。而這個(gè)就是與對(duì)象中的 屬性 Property
是一樣的概念。
Attribute 和 Property 是不是一樣的呢?有的時(shí)候是,有的時(shí)候也不是,這個(gè)完全取決于組件體系的設(shè)計(jì)者。組件的實(shí)現(xiàn)者或者是設(shè)計(jì)者可以讓
attribute
和property
統(tǒng)一。甚至我們把state
、config
、attribute
、property
四者都全部統(tǒng)一也是可以的。
然后就是 方法 method
,它是用于描述一個(gè)復(fù)雜的過(guò)程,但是在 JavaScript 當(dāng)中的 Property
是允許有 get
和 set
這樣的方法的,所以最終 method
和 property
兩者的作用也是差不多的。
那么這里我們可以確定一個(gè)概念,使用組件的開發(fā)者會(huì)使用到 method
和 property
,這些組件的要素。但是如果一個(gè)開發(fā)組件的開發(fā)者需要傳遞一個(gè)消息給到使用組件的程序員,這個(gè)時(shí)候就需要用到 事件 event
。當(dāng)一個(gè)組件內(nèi)部因?yàn)槟撤N行為或者事件觸發(fā)到了變化時(shí),組件就會(huì)給使用者發(fā)送 event
消息。所以這里的 event
的方向就是反過(guò)來(lái)的,從組件往外傳輸?shù)摹?/p>
通過(guò)這張圖我們就可以清楚知道組件的各個(gè)要素的作用,以及他們的信息流轉(zhuǎn)方向。
特性 Attribute
在所有組件的要素中,最復(fù)雜的無(wú)非就是 Attribute
和 Property
。
我們從 Attribute
這個(gè)英文單詞的理解上,更多是在強(qiáng)調(diào)描述性。比如,說(shuō)我們描述一個(gè)人,頭發(fā)很多、長(zhǎng)相很帥、皮膚很白,這些都是屬于 Attribute
,也可以說(shuō)是某一樣?xùn)|西的特性和特征方面的描述。
而 Property
跟多的是一種從屬關(guān)系。比如我們?cè)陂_發(fā)中經(jīng)常會(huì)發(fā)現(xiàn)一個(gè)對(duì)象,它有一個(gè) Property
是另外一個(gè)對(duì)象,那么大概率它們之間是有一個(gè)從屬關(guān)系的,子對(duì)象是從屬于父對(duì)象。但是這里也有一種特殊情況,如果我們是弱引用的話,一個(gè)對(duì)象引用了另外一個(gè)對(duì)象,這樣就是完全是另一個(gè)概念了。
上面講的就是這兩個(gè)詞在英文中的區(qū)別,但是在實(shí)際運(yùn)用場(chǎng)景里面他們也是有區(qū)別的。
因?yàn)?Property
是從屬關(guān)系的,所以經(jīng)常會(huì)在我們面向?qū)ο罄锩媸褂谩6?Attribute
最初就是在我們 XML 里面中使用。它們有些時(shí)候是相同的,有些時(shí)候又是不同的。
Attribute 對(duì)比 Property
這里我們用一些例子來(lái)看看 Attribute 和 Property 的區(qū)別。我們可以看看它們?cè)?HTML 當(dāng)中不等效的場(chǎng)景。
Attribute:
1
2
3
4
5
|
<my-component attribute= "v" /> <script> myComponent.getAttribute( 'a' ) myComponent.setAttribute( 'a' , value) </script> |
- HTML 中的 Attribute 是可以通過(guò) HTML 屬性去設(shè)置的
- 同時(shí)也可以通過(guò) JavaScript 去設(shè)置的
Property:
1
|
myComponent.a = 'value' ; |
- 這里就是定義某一個(gè)元素的 a = ‘value'
- 這個(gè)就不是 attribute 了,而是 property
很多同學(xué)都認(rèn)為這只是兩種不同的寫法,其實(shí)它們的行為是有區(qū)別的。
Class 屬性
1
2
3
4
5
|
< div class = "class1 class2" ></ div > < script > var div = document.getElementByTagName('div'); div.className // 輸出就是 class1 class2 </ script > |
早年 JavaScript 的 Class 是一個(gè)關(guān)鍵字,所以早期 class 作為關(guān)鍵詞是不允許做為屬性名的。但是現(xiàn)在這個(gè)已經(jīng)被改過(guò)來(lái)了,關(guān)鍵字也是可以做屬性名的。
為了讓這個(gè)關(guān)鍵字可以這么用,HTML 里面就做了一個(gè)妥協(xié)的設(shè)計(jì)。在 HTML 中屬性仍然叫做 class
但是在 DOM 對(duì)象中的 property 就變成了 className
。但是兩者還是一個(gè)互相反射的關(guān)系的,這個(gè)神奇的關(guān)系會(huì)經(jīng)常讓大家掉一些坑里面。
比如說(shuō)在 React 里面,我們寫 className它自動(dòng)就把 Class 給設(shè)置了。
Style 屬性
現(xiàn)在 JavaScript 語(yǔ)言中,已經(jīng)沒(méi)有 class 和 className 兩者不一致的問(wèn)題了。我們是可以使用 div.class
這樣的寫法的。但是 HTML 中就還是不支持 class 這個(gè)名字的,這個(gè)也就是一些歷史包袱導(dǎo)致的問(wèn)題。
有些時(shí)候 Attribute 是一個(gè)字符串,而在 Property 中就是一個(gè)字符串語(yǔ)義化之后的對(duì)象。最典型的就是 Style
。
1
2
3
4
5
|
< div class = "class1 class2" style = "color:blue" ></ div > < script > var div = document.getElementByTagName('div'); div.style // 這里就是一個(gè)對(duì)象 </ script > |
在 HTML 里面的 Style 屬性他是一個(gè)字符串,同時(shí)我們可以使用 getAttribute 和 setAttribute 去取得和設(shè)置這個(gè)屬性。但是如果我們用這個(gè) Style 屬性,我們就會(huì)得到一個(gè) key 和 vaule 的結(jié)構(gòu)。
Href 屬性
在 HTML 中 href
的 attribute 和 property 的意思就是非常相似的。但是它的 property 是經(jīng)過(guò) resolve 過(guò)的 url。
比如我們的 href 的值輸入的是 “//m.taobao.com”。這個(gè)時(shí)候前面的 http 或者是 https 協(xié)議是根據(jù)當(dāng)前的頁(yè)面做的,所以這里的 href 就需要編譯一遍才能響應(yīng)當(dāng)前頁(yè)面的協(xié)議。
做過(guò) http 到 https 改造的同學(xué)應(yīng)該都知道,在讓我們的網(wǎng)站使用 https 協(xié)議的時(shí)候,我們需要把所有寫死的 http 或者 https 的 url 都要改成使用 //
。
所以在我們 href 里面寫了什么就出來(lái)什么的,就是 attribute。如果是經(jīng)過(guò) resolve 的就是我們的 property 了。
1
2
3
4
5
6
7
8
9
10
|
<a href= "//m.taobao.com" rel= "external nofollow" ></a> <script> var a = document.getElementByTagName( 'a' ); // 這個(gè)獲得的結(jié)果就是 "http://m.taobao.com", 這個(gè) url 是 resolve 過(guò)的結(jié)果 // 所以這個(gè)是 Property a.href; // 而這個(gè)獲得的是 "//m.taobao.com", 跟 HTML 代碼中完全一致 // 所以這個(gè)是 Attribute a.getAttribute( 'href' ); </script> |
在上面的代碼中我們也可以看到,我們可以同時(shí)訪問(wèn) property 和 attribute。它們的語(yǔ)義雖然非常的接近,但是它們不是一樣的東西。
不過(guò)如果我們更改了任何一方,都會(huì)讓另外一方發(fā)生改變。這個(gè)是需要我們?nèi)プ⒁獾默F(xiàn)象。
Input 和 value
這個(gè)是最神奇的一對(duì),而 value 也是特別的坑。
我們很多都以為 property 和 attribute 中的 value 都是完全等效的。其實(shí)不是的,這個(gè) attribute 中的 input 的 value 相當(dāng)于一個(gè) value 的默認(rèn)值。不論是用戶在 input 中輸入了值,還是開發(fā)者使用 JavaScript 對(duì) input 的 value 進(jìn)行賦值,這個(gè) input 的 attribute 是不會(huì)跟著變的。
而在 input 的顯示上是會(huì)優(yōu)先顯示 property,所以 attribute 中的 value 值就相當(dāng)于一個(gè)默認(rèn)值而已。這就是一個(gè)非常著名的坑,早期同學(xué)們有使用過(guò) JQuery 的話,我們會(huì)覺得里面的 prop 和 attr 是一樣的,沒(méi)想到在 value 這里就會(huì)踩坑。
所以后來(lái) JQuery 庫(kù)就出了一個(gè)叫 val 的方法,這樣我們就不需要去想 attribute 還是 property 的 value,直接用它提供的 val 取值即可。
這里一方面是一起增強(qiáng)一下 HTML 的 property 和 attribute 的知識(shí)。另一方面就是讓我們認(rèn)識(shí)到,就算是非常頂級(jí)的計(jì)算機(jī)專家設(shè)計(jì)的標(biāo)簽系統(tǒng),也出現(xiàn)兩個(gè)差不多的屬性不等效的問(wèn)題。那么如果讓我們?nèi)ピO(shè)計(jì)一個(gè)標(biāo)簽系統(tǒng),我們會(huì)讓 property 和 attribute 等效還是不等效呢? 等學(xué)習(xí)完整個(gè)組件化的知識(shí)后,我們一起來(lái)回答一下這個(gè)問(wèn)題。
如何設(shè)計(jì)組件狀態(tài)
這里我們來(lái)分析一下,property
、attribute
、state
、config
在組件設(shè)計(jì)中都有什么區(qū)別。
這里 Winer 老師給我們整理了一個(gè)表格,分成了四個(gè)場(chǎng)景:
- Markup set —— 用標(biāo)簽去設(shè)置
- JavaScript Set —— 使用 JavaScript 代碼去設(shè)置
- JavaScript Change —— 使用 JavaScript 代碼去改變
- User Input Change —— 終端用戶的輸入而改變
Markup set | JavaScript set | JavaSscript Change | User Input Change | |
---|---|---|---|---|
? | ? | ? | ? | property |
? | ? | ? | ? | attribute |
? | ? | ? | ? | state |
? | ? | ? | ? | config |
那么我們一個(gè)一個(gè)來(lái)講述一下:
- Property
? 它是不能夠被 markup 這種靜態(tài)的聲明語(yǔ)言去設(shè)置的
? 但是它是可以被 JavaScript 設(shè)置和改變的
? 大部分情況下 property 是不應(yīng)該由用戶的輸入去改變的,但是小數(shù)情況下,可能是來(lái)源于我們的業(yè)務(wù)邏輯,才有可能會(huì)接受用戶輸入的改變
- Attribute
? 用戶的輸入就不一定會(huì)改變它,與 Property 同理
? 是可以由 markup,JavaScript 去設(shè)置的,同時(shí)也是可以被 JavaScript 所改變的
- State
? 狀態(tài)是會(huì)由組件內(nèi)部去改變的,它不會(huì)從組件的外部進(jìn)行改變。如果我們想設(shè)計(jì)一個(gè)組件是從外部去改變組件的狀態(tài)的話,那么我們組件內(nèi)部的 state 就失控了。因?yàn)槲覀儾恢澜M件外部什么時(shí)候會(huì)改變我們組件的 state,導(dǎo)致我們 state 的一致性無(wú)法保證。
? 但是作為一個(gè)組件的設(shè)計(jì)者和實(shí)踐者,我們一定要保證用戶輸入是能改變我們組件的 state 的。比如說(shuō)用戶點(diǎn)擊了一個(gè) tab,然后點(diǎn)中的 tab 就會(huì)被激活,這種交互一般都會(huì)用 state 去控制的。
- Config
? Config 在組件中是一個(gè)一次性生效的東西,它只會(huì)在我們組件構(gòu)造的時(shí)候觸發(fā)。所以它是不可更改的。也是因?yàn)樗牟豢筛男裕晕覀兺ǔ?huì)把 config 留給全局。通常每個(gè)頁(yè)面都會(huì)有一份 config,然后拿著這個(gè)在頁(yè)面內(nèi)去使用。
組件生命周期 Lifecycle
講到生命周期,我們最容易想到的會(huì)有兩個(gè),一個(gè)是 created
一個(gè)是 destroy
。世界萬(wàn)物的生命必定會(huì)有 出生
和 死亡
,這兩個(gè)生命周期。
那么在這兩個(gè)開始與結(jié)束之間有什么生命周期呢?我們就需要想一下,一個(gè)組件在構(gòu)造到銷毀之間都會(huì)發(fā)生什么事情。
一個(gè)組件有一個(gè)非常重要的事情,就是它被創(chuàng)建之后,它有沒(méi)有被顯示出來(lái)。這里就涉及生命周期中的 mount
,也就是組件有沒(méi)有被掛載到 “屏幕的這棵樹上”。這個(gè)生命周期我們可以在 React 和 Vue 里面看到,我們經(jīng)常會(huì)使用這個(gè)生命周期,在組件被掛載后做一些相應(yīng)的初始化操作。
有掛載那必然就會(huì)有卸載,所以組件中的 mount
和 unmount
是一組生命周期。而這個(gè)掛載與卸載的整個(gè)生命周期是可以反復(fù)的發(fā)生的,我們可以掛上去然后卸下來(lái),然后再掛上去,這樣反復(fù)又反復(fù)的走這個(gè)生命周期。
所以在 unmount
之后,我們是可以回到 created
構(gòu)建組件的這個(gè)生命周期的狀態(tài)。
那么組件還會(huì)在什么時(shí)候發(fā)生狀態(tài)更變呢?這里我們就有兩種情況:
- 程序員使用代碼去改變或者設(shè)置這個(gè)組件的狀態(tài)
- 用戶輸入時(shí)影響了組件的狀態(tài)
比如說(shuō)我們用戶點(diǎn)了一下按鈕或者 Tab,這個(gè)時(shí)候就會(huì)觸發(fā)這個(gè)組件的狀態(tài)更變。同時(shí)也會(huì)產(chǎn)生一個(gè)組件的生命周期,而這個(gè)生命周期就是 Render 渲染或者 Update 更新。
所有這些生命周期加在一起就是我們一個(gè)組件完整的生命周期。我們看到的所謂 willMount
、didMount
無(wú)非就是這個(gè)生命周期之中更細(xì)節(jié)的位置。下面我給大家附上一張完整的生命周期的圖。
Children
最后我們來(lái)講一下 Children (子組件)的概念。Children 是構(gòu)建組件樹最重要的一個(gè)組件特性,并且在使用中其實(shí)有兩種類型的 Children:
- Content 型 Children —— 我們有幾個(gè) Children,但是最終就能顯示出來(lái)幾個(gè) Children。這種類型的 Children,它的組件樹是非常簡(jiǎn)單的。
-
Template 型 Children —— 這個(gè)時(shí)候整個(gè) Children 它充當(dāng)了一個(gè)模版的作用。比如說(shuō)我們?cè)O(shè)計(jì)一個(gè)
list
,但是最后的結(jié)果不一定就與我們 Children 代碼中寫的一致。因?yàn)槲覀?List 肯定是用于多個(gè)列表數(shù)據(jù)的,所以 list 的表示數(shù)量是與我們傳入組件的 data 數(shù)據(jù)所相關(guān)的。如果我們有 100 個(gè)實(shí)際的 children 時(shí),我們的 list 模版就會(huì)被復(fù)制 100 份。
在設(shè)計(jì)我們的組件樹的 children 的時(shí)候,一定要考慮到這兩種不同的場(chǎng)景。比如我們?cè)?React中,它沒(méi)有 template 型的 children,但是它的 children 可以傳函數(shù),然后這個(gè)函數(shù)可以返回一個(gè) children。這個(gè)時(shí)候它就充當(dāng)了一個(gè)模版型 children 的作用了。那么在 Vue 里面當(dāng)我們?nèi)プ鲆恍o(wú)盡的滾動(dòng)列表的時(shí)候,這個(gè)對(duì) Vue 的模版型 children 就有一定的要求。
結(jié)束語(yǔ)
這里我們就學(xué)習(xí)完了整個(gè)組件的概念和知識(shí)了,下一篇文章我們就會(huì)一起來(lái)設(shè)計(jì)和搭建一個(gè)組件系統(tǒng),并且了解到它的各方各面的實(shí)踐知識(shí)。我們還會(huì)用一些典型的組件和典型的功能來(lái)讓大家對(duì)組件的實(shí)現(xiàn)有一定的了解。
我們?cè)谶@里互相監(jiān)督,互相鼓勵(lì),互相努力走上人生學(xué)習(xí)之路,讓學(xué)習(xí)改變我們生活!
學(xué)習(xí)的路上,很枯燥,很寂寞,但是希望這樣可以給我們彼此帶來(lái)多一點(diǎn)陪伴,多一點(diǎn)鼓勵(lì)。我們一起加油吧!
到此這篇關(guān)于前端組件化基礎(chǔ)知識(shí)的文章就介紹到這了,更多相關(guān)前端組件化,前端基礎(chǔ)知識(shí),前端知識(shí)內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://tridiamond.blog.csdn.net/article/details/112023664