以下是正文~
這裡我們一起來學習前端元件化的知識,而元件化在前端架構裡面是最重要的一個部分。
講到前端架構,其實前端架構中最熱門的就有兩個話題,一個就是元件化,另一個就是架構模式。元件化的概念是從開始研究如何擴充 HTML 标簽開始的,最後延伸出來的一套前端架構體系。而它最重要的作用就是提高前端代碼的複用性。
架構模式就是大家特别熟悉的
MVC
,
MVVM
等設計模式,這個話題主要關心的就是前端跟資料邏輯層之間的互動。
是以說,前端架構當中,元件化可以說是重中之重。在實際工程當中,其實元件化往往會比架構模式要更重要一些。因為元件化直接決定了一個前端團隊代碼的複用率,而一個好的元件化體系是可以幫助一個前端團隊提升他們代碼的複用率,進而也提升了團隊的整體效率。
因為複用率提高了,大家重複編寫的代碼量就會降低,效率就會提高,進而團隊中的成員的心理和心智負擔就會少很多。
!! 是以學習元件化可以是說是非常重要的
這裡我們先從了解什麼是元件化和一個元件的基本組成部分開始。
元件的基本概念
元件都會區分為子產品和對象,元件是與 UI 強相關的,是以某種意義上我們可以認為元件是特殊的子產品或者是特殊的對象。
!! 元件化既是也是
對象
子產品
元件化的特點是可以使用樹形結構來進行組合,并且有一定的模版化的配置能力。這個就是我們元件的一個基本概念。
對象與元件的差別
首先我們來看對象,它有三大要素:
- 屬性 —— Properties
- 方法 —— Methods
- 繼承關系 —— Inherit
在 JavaScript 中的普通對象可以用它的屬性,方法和繼承關系來描述。而這裡面的繼承,在 JavaScript 中是使用原型繼承的。
這裡說的 “普通對象” 不包含複雜的函數對象或者是其他的特殊對象,而在 JavaScript 當中,屬性和方法是一體的。
相對比元件,元件裡面包含的語義要素會更豐富一點,元件中的要素有:
- 屬性 —— Properties
- 方法 —— Methods
- 繼承 —— Inherit
- 特性 —— Attribute
- 配置與狀态 —— Config & State
- 事件 —— Event
- 生命周期 —— Lifecycle
- 子元件 —— Children
Properties
和
Attribute
在英語的含義中是有很大的差別的,但是往往都會翻譯成 “屬性”。如果遇到兩個單詞都出現的時候,就會把
Attribute
翻譯為 “特性”,把
Properties
翻譯成 “屬性”。這兩個要素要怎麼區分呢?這裡在文章的後面會和大家一起詳細了解。
接下來就是元件的
Config
,它就是對元件的一種配置。我們經常會在一個構造函數建立一個對象的時候用到
Config
,我們傳入這個構造函數的參數就叫 “
Config
”(配置)。
同時元件也會有
state
(狀态)。當使用者去操作或者是一些方法被調用的時候,一個
state
就會發生變化。這種就是元件的狀态,是會随着一些行為而改變的。而
state
和
properties
、
attributes
、
config
都有可能是相識或者相同的。
event
就是 “事件” 的意識,而一個事件是元件往外傳遞的。我們的元件主要是用來描述 UI 這樣的東西,基本上它都會有這種事件來實作它的某種類型的互動。
每一個元件都會有生命周期
lifecycle
,這個一會兒在文章的後面會詳細的展開學習。
元件的
children
是非常重要的一部分,
children
也是元件當中一個必要的條件,因為沒有
children
元件就不可能形成樹形結構,那麼描述界面的能力就會差很多。
之前有一些比較流行的拖拽系統,我們可以把一些寫好的 UI 元件拖到頁面上,進而建立我們的系統界面。但是後面發現除了可以拖拽在某些區域之外,還需要一些自動排序,元件嵌套元件的功能需求。這個時候元件與元件之間沒有樹形結構就不好使了。
最後元件在對象的基礎上添加了很多語義相關的概念,也是這樣使得元件變成了一種非常适合描述 UI 的概念。
元件 Component
我們用一張圖來更深入的了解元件。

元件最直接産生變化的來源就是使用者的輸入和操作,比如說當一個使用者在我們的選擇框元件中選中了一個選項時,這個時候我們的狀态
state
,甚至是我們的子元件
children
都會發生變化。
圖中右邊的這幾種情況就是元件的開發者與元件的關系。其中一種就是開發者使用了元件的标記代碼
Markup Code
,來對元件産生影響。其實,也就是開發者通過元件特性
Attribute
來更改元件的一些特征或者是特性。
!! Attribute 是一種聲明型的語言,也是 标記型代碼 Markup Code
。而 Markup Code 也不一定是我們的 HTML 這種 XML 類的語言。在标記語言的大生态中,其實有非常多的語言可以用來描述一個界面的結構。但是最主流的就是基于 XML 體系的。在我們 Web 領域裡面最常見的就是 XML 。而 JSX 也可以了解為一種嵌入在程式設計語言裡面的 XML 結構。
開發者除了可以用
Attribute
,也可以用 Property 來影響元件。這個元件本身是有
Property
(屬性)的,當開發者去修改一個元件的屬性時,這個元件就會發生變化。而這個就是與對象中的
屬性 Property
是一樣的概念。
!! Attribute 和 Property 是不是一樣的呢?有的時候是,有的時候也不是,這個完全取決于元件體系的設計者。元件的實作者或者是設計者可以讓 和
attribute
統一。甚至我們把
property
、
state
、
config
、
attribute
四者都全部統一也是可以的。
property
然後就是
方法 method
,它是用于描述一個複雜的過程,但是在 JavaScript 當中的
Property
是允許有
get
和
set
這樣的方法的,是以最終
method
和
property
兩者的作用也是差不多的。
那麼這裡我們可以确定一個概念,使用元件的開發者會使用到
method
和
property
,這些元件的要素。但是如果一個開發元件的開發者需要傳遞一個消息給到使用元件的程式員,這個時候就需要用到
事件 event
。當一個元件内部因為某種行為或者事件觸發到了變化時,元件就會給使用者發送
event
消息。是以這裡的
event
的方向就是反過來的,從元件往外傳輸的。
通過這張圖我們就可以清楚知道元件的各個要素的作用,以及他們的資訊流轉方向。
特性 Attribute
在所有元件的要素中,最複雜的無非就是
Attribute
和
Property
。
我們從
Attribute
這個英文單詞的了解上,更多是在強調描述性。比如說我們描述一個人,頭發很多、長相很帥、皮膚很白,這些都是屬于
Attribute
,也可以說是某一樣東西的特性和特征方面的描述。
而
Property
更多的是一種從屬關系。比如,我們在開發中經常會發現一個對象,它有一個
Property
是另外一個對象,那麼大機率它們之間是有一個從屬關系的,子對象是從屬于父對象。但是這裡也有一種特殊情況,如果我們是弱引用的話,一個對象引用了另外一個對象,這樣就是完全是另一個概念了。
上面講的就是這兩個詞在英文中的差別,但是在實際運用場景裡面他們也是有差別的。
因為
Property
是從屬關系的,是以經常會在我們面向對象裡面使用。而
Attribute
最初就是在我們 XML 裡面中使用。它們有些時候是相同的,有些時候又是不同的。
Attribute 對比 Property
這裡我們用一些例子來看看 Attribute 和 Property 的差別。我們可以看看它們在 HTML 當中不等效的場景。
Attribute:
<my-component attribute="v" />
<script>
myComponent.getAttribute('a')
myComponent.setAttribute('a', value)
</script>
- HTML 中的 Attribute 是可以通過 HTML 屬性去設定的
- 同時也可以通過 JavaScript 去設定的
Property:
myComponent.a = 'value';
- 這裡就是定義某一個元素的 a = ‘value’
- 這個就不是 attribute 了,而是 property
!! 很多同學都認為這隻是兩種不同的寫法,其實它們的行為是有差別的。
Class 屬性
<div class="class1 class2"></div>
<script>
var div = document.getElementByTagName('div');
div.className // 輸出就是 class1 class2
</script>
早年 JavaScript 的 Class 是一個關鍵字,是以早期 class 作為關鍵詞是不允許做為屬性名的。但是現在這個已經被改過來了,關鍵字也是可以做屬性名的。
為了讓這個關鍵字可以這麼用,HTML 裡面就做了一個妥協的設計。在 HTML 中屬性仍然叫做
class
但是在 DOM 對象中的 property 就變成了
className
。但是兩者還是一個互相反射的關系的,這個神奇的關系會經常讓大家掉一些坑裡面。
比如說在 React 裡面,我們寫 className它自動就把 Class 給設定了。
Style 屬性
現在 JavaScript 語言中,已經沒有 class 和 className 兩者不一緻的問題了。我們是可以使用
div.class
這樣的寫法的。但是 HTML 中就還是不支援 class 這個名字的,這個也就是一些曆史包袱導緻的問題。
有些時候 Attribute 是一個字元串,而在 Property 中就是一個字元串語義化之後的對象。最典型的就是
Style
。
<div class="class1 class2" style="color:blue"></div>
<script>
var div = document.getElementByTagName('div');
div.style // 這裡就是一個對象
</script>
在 HTML 裡面的 Style 屬性他是一個字元串,同時我們可以使用 getAttribute 和 setAttribute 去取得和設定這個屬性。但是如果我們用這個 Style 屬性,我們就會得到一個 key 和 vaule 的結構。
Href 屬性
在 HTML 中
href
的 attribute 和 property 的意思就是非常相似的。但是它的 property 是經過 resolve 過的 url。
比如我們的 href 的值輸入的是 "//m.taobao.com"。這個時候前面的 http 或者是 https 協定是根據目前的頁面做的,是以這裡的 href 就需要編譯一遍才能響應目前頁面的協定。
做過 http 到 https 改造的同學應該都知道,在讓我們的網站使用 https 協定的時候,我們需要把所有寫死的 http 或者 https 的 url 都要改成使用
//
。
是以在我們 href 裡面寫了什麼就出來什麼的,就是 attribute。如果是經過 resolve 的就是我們的 property 了。
<a href="//m.taobao.com"></a>
<script>
var a = document.getElementByTagName('a');
// 這個獲得的結果就是 "http://m.taobao.com", 這個 url 是 resolve 過的結果
// 是以這個是 Property
a.href;
// 而這個獲得的是 "//m.taobao.com", 跟 HTML 代碼中完全一緻
// 是以這個是 Attribute
a.getAttribute('href');
</script>
在上面的代碼中我們也可以看到,我們可以同時通路 property 和 attribute。它們的語義雖然非常的接近,但是它們不是一樣的東西。
不過如果我們更改了任何一方,都會讓另外一方發生改變。這個是需要我們去注意的現象。
Input 和 value
這個是最神奇的一對,而 value 也是特别的坑。
我們很多都以為 property 和 attribute 中的 value 都是完全等效的。其實不是的,這個 attribute 中的 input 的 value 相當于一個 value 的預設值。不論是使用者在 input 中輸入了值,還是開發者使用 JavaScript 對 input 的 value 進行指派,這個 input 的 attribute 是不會跟着變的。
而在 input 的顯示上是會優先顯示 property,是以 attribute 中的 value 值就相當于一個預設值而已。這就是一個非常著名的坑,早期同學們有使用過 JQuery 的話,我們會覺得裡面的 prop 和 attr 是一樣的,沒想到在 value 這裡就會踩坑。
是以後來 JQuery 庫就出了一個叫 val 的方法,這樣我們就不需要去想 attribute 還是 property 的 value,直接用它提供的 val 取值即可。
!! 這裡一方面是一起增強一下 HTML 的 property 和 attribute 的知識。另一方面就是讓我們認識到,就算是非常頂級的計算機專家設計的标簽系統,也出現兩個差不多的屬性不等效的問題。那麼如果讓我們去設計一個标簽系統,我們會讓 property 和 attribute 等效還是不等效呢?
如何設計元件狀态
這裡我們來分析一下,
property
、
attribute
、
state
、
config
在元件設計中都有什麼差別。
這裡 Winer 老師給我們整理了一個表格,分成了四個場景:
- Markup set —— 用标簽去設定
- JavaScript Set —— 使用 JavaScript 代碼去設定
- JavaScript Change —— 使用 JavaScript 代碼去改變
- User Input Change —— 終端使用者的輸入而改變
Markup set | JavaScript set | JavaSscript Change | User Input Change | |
❌ | ✅ | ✅ | ❓ | property |
✅ | ✅ | ✅ | ❓ | attribute |
❌ | ❌ | ❌ | ✅ | state |
❌ | ✅ | ❌ | ❌ | config |
那麼我們一個一個來講述一下:
- Property
- ❌ 它是不能夠被 markup 這種靜态的聲明語言去設定的
- ✅ 但是它是可以被 JavaScript 設定和改變的
- ❓ 大部分情況下 property 是不應該由使用者的輸入去改變的,但是小數情況下,可能是來源于我們的業務邏輯,才有可能會接受使用者輸入的改變
- Attribute
- ❓ 使用者的輸入就不一定會改變它,與 Property 同理
- ✅ 是可以由 markup,JavaScript 去設定的,同時也是可以被 JavaScript 所改變的
- State
- ❌ 狀态是會由元件内部去改變的,它不會從元件的外部進行改變。如果我們想設計一個元件是從外部去改變元件的狀态的話,那麼我們元件内部的 state 就失控了。因為我們不知道元件外部什麼時候會改變我們元件的 state,導緻我們 state 的一緻性無法保證。
- ✅ 但是作為一個元件的設計者和實踐者,我們一定要保證使用者輸入是能改變我們元件的 state 的。比如說使用者點選了一個 tab,然後點中的 tab 就會被激活,這種互動一般都會用 state 去控制的。
- Config
- ✅ Config 在元件中是一個一次性生效的東西,它隻會在我們元件構造的時候觸發。是以它是不可更改的。也是因為它的不可更改性,是以我們通常會把 config 留給全局。通常每個頁面都會有一份 config,然後拿着這個在頁面内去使用。
元件生命周期 Lifecycle
講到生命周期,我們最容易想到的會有兩個,一個是
created
一個是
destroy
。世界萬物的生命必定會有
出生
和
死亡
,這兩個生命周期。
那麼在這兩個開始與結束之間有什麼生命周期呢?我們就需要想一下,一個元件在構造到銷毀之間都會發生什麼事情。
一個元件有一個非常重要的事情,就是它被建立之後,它有沒有被顯示出來。這裡就涉及生命周期中的
mount
,也就是元件有沒有被挂載到 “螢幕的這棵樹上”。這個生命周期我們可以在 React 和 Vue 裡面看到,我們經常會使用這個生命周期,在元件被挂載後做一些相應的初始化操作。
有挂載那必然就會有解除安裝,是以元件中的
mount
和
unmount
是一組生命周期。而這個挂載與解除安裝的整個生命周期是可以反複的發生的,我們可以挂上去然後卸下來,然後再挂上去,這樣反複又反複的走這個生命周期。
是以在
unmount
之後,我們是可以回到
created
建構元件的這個生命周期的狀态。
那麼元件還會在什麼時候發生狀态更變呢?這裡我們就有兩種情況:
- 程式員使用代碼去改變或者設定這個元件的狀态
- 使用者輸入時影響了元件的狀态
比如說我們使用者點了一下按鈕或者 Tab,這個時候就會觸發這個元件的狀态更變。同時也會産生一個元件的生命周期,而這個生命周期就是 Render 渲染或者 Update 更新。
所有這些生命周期加在一起就是我們一個元件完整的生命周期。我們看到的所謂
willMount
、
didMount
無非就是這個生命周期之中更細節的位置。下面我給大家附上一張完整的生命周期的圖。
Children
最後我們來講一下 Children(子元件)的概念。Children 是建構元件樹最重要的一個元件特性,并且在使用中其實有兩種類型的 Children:
- Content 型 Children —— 我們有幾個 Children,但是最終就能顯示出來幾個 Children。這種類型的 Children,它的元件樹是非常簡單的。
- Template 型 Children —— 這個時候整個 Children 它充當了一個模版的作用。比如說我們設計一個
,但是最後的結果不一定就與我們 Children 代碼中寫的一緻。因為我們 List 肯定是用于多個清單資料的,是以 list 的表示數量是與我們傳入元件的 data 資料所相關的。如果我們有 100 個實際的 children 時,我們的 list 模版就會被複制 100 份。list