weex-html5元件進階
前言
上一篇文章《weex-html5 擴充套件開發指引》中介紹了 weex-html5 擴充套件元件、模組的基本步驟和方法。在元件擴充套件的內容裡提了幾個擴充套件元件的關鍵性的問題,這幾個問題涉及到元件的實現以及一些原理和工具。本篇將會就 weex-html5 元件的基類、管理類、元件渲染的執行流程以及一些重要的注意事項和最佳實踐展開討論。
先來回味一下前篇中提到的,在元件擴充套件過程中可能遇到的問題:
- 在元件的 constructor 裡需要幹些什麼?
- 在元件的其他方法中分別需要做哪些事情?
- 有哪些可以直接呼叫的父類的原型方法?
- 元件從註冊到渲染到頁面上的執行流程是怎樣的?
先不著急回答這幾個問題,我們先來了解一下 weex-html5 元件架構的基本原理。
元件基礎
weex-html5 的所有元件都是從一個最基礎的基類繼承而來,基類中包含基本的 渲染操作 和一些 __輔助方法__。每個元件都有一個 id 用於 jsfm (weex-jsframework) 對其進行索引。在web渲染端,管理這些索引,以及響應 jsfm 的操作指令,並做一些元件渲染週期的管理,這些事情是由元件的管理者 ComponentManager
負責的。每一個 weex 的例項都包含一個 ComponentManager
的例項。
基類 Component 和 Atomic
每個元件在定義的時候都需要實現一個 init
方法,用於 weex 對該元件進行註冊。在 weex.install(yourComponent)
的過程中會向這個方法裡注入 Weex 這個類。你可以通過 Weex.Component
獲取到這個類的建構函式,也可以通過 Weex.Atomic
獲取 Atomic 類的建構函式(其他暴露在 Weex 上的靜態屬性還包括 ComponentManager
, utils
以及 config
)。
Component 是 weex-html5 自定義元件的始祖,一切元件包括 Atomic 都是從這個基類繼承而來。 Atomic 是 不包含任何子元件 的元件,相比 Component 來說有更嚴格的限制,並且不需要重寫它的 createChildren
、appendChild
、inserBefore
以及 removeChild
等操作子節點的方法。簡單來說,如果你要定義一個可以有子元件的元件,那麼繼承 Component 就可以,如果你要定義一個不應當包含任何子元件的元件(比如表單元件 input),那麼需要繼承 Atomic.
下面對這些方法分別進行介紹:
獲取其他關鍵資訊的方法
-
getWeexInstance
獲取當前的 weex 例項 -
getComponentManager
獲取當前 weex 例項對應的 ComponentManager 例項
用於元件索引的方法
-
getParent
獲取父元件 -
getParentScroller
向上獲取最近的 scrollable 元件(滑動元件,目前有 list、scroller 等 ),如果不在 scrollable 元件內部,則返回 null -
getRootScroller
獲取最頂層的 scrollable 元件 -
getRootContainer
獲取當前 weex 頁面的 root 節點,一般是document.body
下 id 為 weex 的節點 -
isScrollable
當前元件是否是 scrollable 元件 -
isInScrollable
當前元件是否是其他 scrollable 元件的子孫元件
渲染操作相關方法
渲染操作相關方法是 weex 元件渲染執行流程中的重要環節。weex 元件的執行流程可以在 Component 元件的建構函式中找到:
export default function Component (data, nodeType) {
this.data = data
this.node = this.create(nodeType)
this.createChildren()
this.updateAttrs(this.data.attr || {})
const classStyle = this.data.classStyle
classStyle && this.updateStyle(classStyle)
this.updateStyle(this.data.style || {})
this.bindEvents(this.data.event || [])
}
從程式碼裡可以看出一個元件的基本構造流程為:
繫結資料 (data) -> 建立節點 (create) -> 建立子節點 (createChildren) -> 更新屬性值 (updateAttrs) -> 更新樣式 (updateStyle) -> 繫結事件 (bindEvents)
這個元件構造完畢後需要掛載到某個頁面中已經存在的父節點中,這時候就會(通過 ComponentManager )呼叫父節點的 appendChild
或 insertBefore
方法,所以這兩個方法也非常重要,但是一般元件不需要重寫這兩個方法,除非需要在這裡做一些特殊的邏輯處理。
-
create
建立當前元件在 weex 頁面中所佔據的具體 dom 節點。比如<image>
元件的 create 方法裡就建立了一個<div>
元素,併為該元素新增了 class 類名。weex 預置了兩個通用的類名weex-container
和weex-element
,用於消除 web 元件和基於css-layout庫的 native 元件之間的樣式差異,建議總是新增這兩個類名中的一個。
weex-container 和 weex-element 類的預設樣式如下:
.weex-container {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
flex-shrink: 0;
align-items: stretch;
box-align: stretch;
align-content: flex-start;
position: relative;
border: 0 solid black;
margin: 0;
padding: 0;
min-width: 0;
}
.weex-element {
box-sizing: border-box;
position: relative;
flex-shrink: 0;
border: 0 solid black;
margin: 0;
padding: 0;
min-width: 0;
}
-
createChildren
建立子節點。這裡僅限於建立 data.children 中的節點,如果當前節點的 append 方式 是append="node"
這種 weex 的預設處理方式,那麼子節點不會被塞到 data.children 裡處理,如果當前節點的 append 方式是append="tree"
方式,此時該節點的子節點都需要父節點通過處理 data.children 來建立。如果你不知道怎麼處理 data.children 裡的每個陣列元素,可以直接丟給ComponentManager.createElement(data.children[i])
來處理。實際上 Component 基類裡就是這麼做的,當然你也可以自己去做一些特殊處理 -
appendChild
新增一個子節點到子節點列表的末尾 -
insertBefore
新增一個子節點到指定的位置 (指定index
) -
removeChild
刪除一個子節點 -
updateAttrs
更新屬性值,不推薦直接重寫此方法,後面會介紹如何配置屬性的 setter -
updateStyle
更新樣式,不推薦直接重寫此方法,後面會介紹如何配置樣式的 setter -
bindEvents
繫結事件,不推薦直接重寫此方法,後面會介紹如何配置事件的額外引數以及 updator -
unbindEvents
解綁事件,一般不需要重寫此方法
元件週期相關方法
-
onAppend
元件被掛載到頁面時的執行勾子,基類已經在這裡做了一些處理,不要直接重寫這個方法 -
addAppendHandler
為元件新增掛載時的執行勾子,如果想要在元件被掛載時執行一些程式碼,可以呼叫這個方法
其他重要方法
-
dispatchEvent
在當前元件的 node 上觸發一個事件(如果 DSL 開發者繫結了這個事件型別的監聽器,那麼這個監聽器會被觸發) -
enableLazyload
為當前元件的 node 節點指定懶載入的屬性img-src
為某個指定的 src,這樣懶載入控制器會識別當前節點為 image 並對當前節點進行懶載入控制,這個一般不會用到,開發者比較常用的是下一個方法 -
fireLazyload
用於手動觸發某個元件或者節點內部的 image 元件進行懶載入。之所以會有這個方法是由於某些特殊情況下元件進入了視口,其中的圖片節點應該進行載入時,懶載入因為某些原因卻沒有正確執行,這種情況下就可以手動呼叫此方法
配置資訊
配置資訊是方便元件對屬性、樣式和事件進行定製的一種手段。通過配置可以簡化程式碼,規範元件行為,避免不必要的程式碼冗餘,提高程式碼複用度,方便開發者進行擴充套件。
-
attr
屬性 setter 配置,基類的為空物件 -
style
基類配置兩組樣式的 setter, 一組和positon
相關,即除了基本的relative
和absolute
,更支援了 position 的fixed
,sticky
值。另外對flex
相關的樣式做了歸一化的處理,開發者不用去寫多套 flex 降級名稱。歸一化後的 flex 樣式及其支援的值為:
樣式名 | 支援的值 |
---|---|
flex | number |
align-items | flex-start, flex-end, stretch, center |
justify-content | flex-start, flex-end, center, space-between |
-
event
事件配置,基類的為空物件
在後面的 元件擴充套件實踐 小節裡對如何做元件配置做了詳細解釋。
管理者componentManager
ComponentManager 是元件的大管家,不僅僅需要管理當前註冊的元件型別,管理當前 weex 例項的所有元件以及它們的 ref id,還要負責監聽元件的 appear 事件,判斷當前的渲染狀態,並串聯處理元件生命週期的各個階段。
靜態方法
ComponentManager 包含幾個靜態方法,可以直接通過 ComponentManager.xxx
呼叫。
-
getInstance(id)
獲取對應 id 的 ComponentManager 例項 -
registerComponent
註冊元件,我們自定義的元件在內部就是通過這個方法註冊進來的 -
getScrollableTypes
獲取 scrollable 元件型別(比如list
,scroller
等)的陣列
例項方法
每個 ComponentManager 例項都實現了 jsfm 裡 vdom 的 Listener 的裡的方法,在 jsfm 裡的 Listener 負責建立對應虛擬 dom 操作的指令傳送給 native 端的 callNative
橋接器。而在 weex-html5 裡 ComponentManager 接管了這個介面,並將 dom 操作的指令轉換為真實的元件增添刪除以及其他操作。
這些元件操作相關的方法,在 native 平臺以及舊版的 weex-html5(< v0.3.0) 中是通過 bridge 的 callNative
接收 dom
模組的 API 呼叫實現的。在新版本的 weex-html5 (>= v0.3.0)中 ComponentManager 接管了 dom
模組的幾乎所有方法(除了 scrollToElement
這個比較特殊的方法)。
這些方法包括:
-
createBody
建立頁面根節點 -
addElement
新增一個元件 -
removeElement
移除一個元件 -
moveElement
移動一個元件 -
setAttr
更新屬性值 -
setStyle
更新樣式 -
setStyles
更新多個樣式 -
addEvent
新增事件監聽 -
removeEvent
移除事件監聽
上述方法對於我們元件開發者來說其實是透明的,一般不會被用到。另一些方法則是你可能會用到的:
-
getComponent
weex 裡每個元件都有一個唯一的 id, 在 weex 裡這個 id 叫做 ref. ComponentManager 內部儲存了一個元件的 ref 和例項對映的 map. 一些針對某個元件進行的操作,比如setAttr
,setStyle
,removeElement
等都會根據這個 map 去查詢對應 ref 的元件。ComponentManager 同時提供了這個專門通過 ref 獲取對應元件的方法。 在元件中可以通過this.getComponentManager().getComponent(ref)
獲取某個元件。 -
createElement
如果你要自己實現元件的appendChild
或者createChildren
等方法,你得到的入參一般是元件的 data,這時呼叫 ComponentManager 的createElement(data)
返回的就是對應data.type
指定型別的元件的例項。這時這個方法是必須呼叫的,因為只有 ComponentManager 中有註冊的元件型別資訊。
另一些你不會直接呼叫,但是在元件裡可能會間接用到的方法:
-
rendering
weex 元件基於優化的考慮會在元件做頻繁 dom 操作期間做一些提高可用性減少頁面阻塞的操作。ComponentManager 通過這個方法向 global (window) 物件註冊了兩個事件,renderbegin
和renderend
. 某個固定時間間隔內(預設為 800ms)沒有任何 dom 操作時會出發renderend
事件,一旦有 dom 操作就會觸發renderbegin
. 這裡的 dom 操作特指addElement
,removeElement
和moveElement
這三個操作。如果你的元件需要在`頻繁 dom 操作期間`做一些優化操作,比如關閉某些特性,可以考慮監聽這兩個事件。 -
handleAppend
在元件掛載之後呼叫onAppend
方法。你不會直接用到這個方法,但是元件的onAppend
的執行依賴這個方法。另外元件的appear
和disappear
事件也是在這裡繫結的,圖片的懶載入也是在這裡觸發。
頁面的構建流程
前面已經介紹了在基類 Component 的建構函式裡的執行流程。這裡跳出單個元件的構造過程,我們來看整個頁面是如何被構造並渲染出來的。這個過程涉及到 jsfm 裡如何編譯模板、繫結資料、監聽變化並構造虛擬 dom 等等,這些原理限於篇幅這裡就不多做介紹了,展開會是個很大的話題。我們把 jsfm 看作一個實體,來看 jsfm 和 render 之間的通訊過程,以及 ComponentManager 例項和各個元件之間的協作過程。
ComponentManager 做為 Listener 掛載在 jsfm 的 vdom(虛擬 dom) 裡,在 jsfm 的 Document 例項裡包含它的引用。vdom 的所有新增刪除元素的操作,都會觸發 ComponentManager 的對應方法,轉變為真實的元件操作。
在 vdom 裡有個 documentElement 的概念,類似 html 裡的 documentElement,相當於整個頁面的根標籤。在這個根標籤裡新增的節點,被稱為 body. 在 append body 的過程中會呼叫 ComponentManager 的 createBody
方法,這時 weex 頁面的根節點就是 createBody
這個方法建立出來的。
當 vdom 裡需要新增一個元素,首先觸發其父元素的 appendChild (Element.prototype.appendChild
) 或者 insertBefore (Element.prototype.insertBefore
) 方法,這個操作被翻譯到 Listener 中,也就是 ComponentManager 的 addElement
方法。在 ComponentManager 中會根據傳入的 index 判斷是呼叫自己的 appendChild
(在末尾新增元素) 還是 insertBefore
(在中間插入元素)。
在 ComponentManager 的 appendChild
和 insertBefore
方法中首先會根據 parentRef 找到父元件,然後呼叫父元件的對應的方法(如果沒有 override 的話就是基類 Component 的對應方法)。在這些方法中又會呼叫 ComponentManager 的 createElement
方法建立要替新增的子元件。
子元件也可能會有子元素,如果 DSL 裡指定了一個元件的 append 屬性為 append=tree
,那麼新增這個元件的時候,它的子元件的資訊都放在了 data.children
裡。這時子元件的構建過程中會呼叫 createChildren
方法建立子元件。反之如果 DSL 不指定 append 或者指定其為 append=node
(預設方式),此時 data.children
一般為空,而它的子元件會通過下一次的 addElement
(ComponentManager.prototype.addElement
) 呼叫被建立。
整個頁面就是從 createBody
開始,接著向 body 節點裡 addElement
新增新的元件,並在這個新的元件裡 addElement
新增它自己的子元件,這樣不斷迭代構造出來的。每個元件再通過自身的構造渲染流程(create
, createChildren
, updateAttrs
, updateStyle
, bindEvents
等等),把自己按照一定的模板和樣式渲染到頁面中。
元件擴充套件實踐
基本原理是很簡單的,但是實際擴充套件元件的過程中,需要關注一些最佳實踐和注意事項。
元件的建構函式
一個元件例項化的入口總是它的建構函式。在基類 Component 和 Atomic 的建構函式裡已經做了大部分的函式呼叫,前面已經提過,它們的執行順序是:
繫結資料 (data) -> 建立節點 (create) -> 建立子節點 (createChildren) -> 更新屬性值 (updateAttrs) -> 更新樣式 (updateStyle) -> 繫結事件 (bindEvents)
在自定義元件的建構函式裡就不需要重複去做這些事情了,只需要呼叫基類的建構函式即可:
function init (Weex) {
const Component = Weex.Component
// ...
// weex-hello-web 的建構函式:
function Hello (data) {
// 在子類的建構函式裡呼叫基類的建構函式
Component.call(this, data)
}
Hello.prototype = Object.create(Component.prototype)
// ...
}
這樣這個元件就可以跑起來。當然你可以向其中新增一些其他的邏輯,比如儲存 data 裡的屬性,做一些初始化的操作,這取決於你元件所承載的功能。建議元件內部抽象的以及資料相關的初始化邏輯放到元件的建構函式裡,而涉及到具體 Dom 構建和操作的初始化邏輯放到 create
方法裡。
配置屬性、樣式和事件
擴充套件一個元件,不僅僅要定製它的建構函式,建立節點及子節點的過程,還要定義它能接受的引數。拿weex-hello這個簡單的元件 demo 做例子,它帶有一個屬性叫做 value
,可以設定的樣式包括 txt-color
和 bg-color
,並在 click
事件的引數裡傳遞了一個 value
值(e.value)。
配置屬性
const attr = {
// 定義 value 這個屬性的 setter
value (val) {
this.value = val
this.inner.textContent = `Hello ${val}!`
}
}
這裡 value
是一個 setter,接受新的屬性值(val
)做為引數,你所要做的事情就是定義這個 setter,每次這個屬性更新的時候這個 setter 都會被執行。在 setter 裡的 this
會繫結為當前的 component 例項。
配置樣式
const style = {
// 定義 text-color 這個樣式的 setter
txtColor (val) {
this.inner.style.color = val
},
// 定義 bg-color 的 setter
bgColor (val) {
this.inner.style.backgroundColor = val
}
}
這裡 txtColor
的 setter 的引數接受的是 txt-color
的樣式值。同理 bgColor
對應的是元件的樣式 bg-color
. 你所要做的事是定義這兩個 setter 的內容。在這個例子裡僅僅是將 this.inner
這個 dom 元素的對應樣式更新為指定的值,複雜元件可能需要你做更多事情。和 attr 的 setter 一樣,函式體裡的 this
會繫結為當前的 component 例項。
配置事件
const event = {
click: {
// 定義 click 事件物件的額外引數
extra () {
return {
value: this.inner.textContent
}
}
}
}
事件配置相比屬性和樣式的配置更復雜一些。首先需要指定對哪個事件型別做配置,例子裡需要定製的事件型別只有一個 click
。一個事件可以進行三種配置,分別為 extra
、updator
和 setter
.
-
extra
配置事件引數傳遞的額外資訊,比如在上面的例子裡需要往 event 物件裡增加一個value
值,DSL 開發者可以通過evt.value
得到這個值:
methods: {
// 在 click 的回撥函式裡獲取 value 值
handleClick (evt) {
console.log(`value is:`, evt.value)
}
}
注意 extra
是一個函式,需要返回一個額外資料物件,這個函式的 this
也是繫結為當前 component 的例項的。
-
updator
weex 目前由於自身的限制無法做到資料雙向繫結,使用者操作導致的資料變更需要 DSL 開發者在事件監聽裡獲取並進行手動更新,而手動更新資料可能導致 jsfm 傳送冗餘的更新操作訊息。updator 可以認為是 weex 的一種資料靜默更新機制,當使用者操作導致某個 attr 或者 style 的值發生變更時,會把對應的值傳給 jsfm ,這樣當 DSL 開發者手動更新資料時 jsfm 已經將該值更新過了,不會再發冗餘的訊息 -
setter
直接替換掉事件監聽函式,並不推薦使用這種方式進行事件繫結
統統繫結到 prototype
定義了 attr, style 和 event,還需要把它們繫結到 prototype 上。這裡也有一些技巧,並不是直接把 prototype 加上這些屬性就可以了。我們來看這段程式碼:
function init (Weex) {
// ...
const extend = Weex.utils.extend
extend(Input.prototype, proto)
extend(Input.prototype, { attr })
extend(Input.prototype, {
style: extend(Object.create(Atomic.prototype.style), style)
})
extend(Input.prototype, { event })
// ...
}
這裡的 extend 就是簡化版的 Object.assign
,這段程式碼很好理解。需要注意的是,style 不是直接掛載到 prototype 上面的,因為基類(這個例子是 Atomic)已經包含 style 屬性,即 position
的 setter 以及 flex
規範化的 setter. 所以需要把原來的 style 都繼承下來,再用 extend 新增新的 style 進來。
螢幕適配係數:scale
weex 是如何適配不同大小螢幕的?這個問題涉及到元件在頁面上的最終展現。如果你擴充套件的元件有自定義的屬性或者樣式,涉及到尺寸大小的,需要非常注意這一塊。每個元件在被建立之前,會由 ComponentManager 將當前螢幕的 scale 值注入元件的 data (在除了 constructor 以外的任何元件方法中都可以通過 this.data.scale
訪問到)中。那麼這個 scale 到底是什麼?
weex 中的設計尺寸是 __750px__,也就是說 weex 認為所有螢幕的寬度都是歸一化的 __750px__. 當真實螢幕不是 750px,weex 會自動將設計尺寸對映到真實尺寸中去,這個 scale
就是這種對映的比例。它的計算公式是 當前螢幕尺寸 / 750
.
所以在擴充套件元件的時候,如果使用者傳入一個尺寸值,比如說 375
,這個值是相對於 750
的設計尺寸來說的。你只需要將這個值乘以 scale, 就是適配當前螢幕的真實尺寸:value = 375 * this.data.scale
. 它應該佔據真實螢幕一半的大小。
元件的生命週期
一個元件的生命週期包括初始化、構建、掛載以及移除等。元件開發者可以在其中各個階段進行控制。
- __初始化__:通過元件的建構函式實現初始化的邏輯控制
- __構建__:通過重寫元件的
create
方法實現構建階段的邏輯控制 - __掛載__:基類 Component 在 onAppend 方法中已經做了一些處理(檢測
appear
事件的觸發條件,如果滿足條件則觸發該事件),如果需要在掛載階段做一些自己的處理,可以在初始化的邏輯裡呼叫addAppendHandler
方法向 onAppend 中新增程式碼:
function MyComponent (data) {
this.addAppendHandler(() => {
// 節點掛載以後需要執行的邏輯
})
Component.call(this, data)
}
這個呼叫操作也可以放在元件繼承完成以後的任何時刻進行:
function init (Weex) {
const Atomic = Weex.Atomic
const extend = Weex.utils.extend
function Input (data) {
Atomic.call(this, data)
}
Input.prototype = Object.create(Atomic.prototype)
extend(Input.prototype, proto)
// 新增 onAppend handler
Input.prototype.addAppendHandler(() => {
// 節點掛載以後需要執行的邏輯
})
// ...
}
- __移除__:在基類的程式碼裡可能找不到這個函式,因為基類沒有做額外的處理。如果你需要在元件被移除時做一些操作,比如連線斷開、資源釋放等,可以為你的元件新增一個
onRemove
方法,元件在被移除時會自動呼叫這個方法。
獲取當前 weex 例項的 id
和 scale 類似,元件在被構建之前就已經在 ComponentManager 的 createElement 方法裡,將 id 注入到元件的 data 裡。在元件的生命週期的任何時刻都可以通過 this.data.instanceId
獲取到當前 weex 例項的 id.
獲取當前 weex 例項
在元件的生命週期任何時刻都可以通過 this.getWeexInstance
獲取到當前 weex 例項。這個方法只是 this.getComponentManager().getWeexInstance()
的簡化版,一般也不太會用到。
獲取 ComponentManager
ComponentManager 包含靜態方法和例項方法。在 init
方法裡可以通過 Weex.ComponentManager
獲取到 ComponentManager 這個類,並在接下來的程式碼裡呼叫它的靜態方法。在自定義元件的方法實現中,可以通過 this.getComponentManager()
獲取到當前 ComponentManager 的例項。一般可能用到的是 createElement
, getComponent
這兩個方法。
utils
utils 是 weex-html5 內部一套工具函式,不是很推薦業務上直接使用。後續可能進行相關的重構,但是有幾個使用比較頻繁的方法應該不會有大的變動:
-
extend(target, ...src)
其實就是Object.assign
,將後面傳入的物件的鍵值對拷貝給 target,相當於 mixin. 需要注意越靠後的物件的鍵值對具有越優先的覆蓋權 -
detectWep
判讀當前裝置是否支援webp
格式圖片 -
detectSticky
判斷當前裝置是否支援原生的position: sticky
-
throttle(func, wait)
函式限流,func
為要限流的函式,wait
為呼叫時間最小間隔,單位為 ms -
isPlainObject
是否是[object Object]
-
getType
返回物件的真實型別,如string
,number
,object
,date
, 等等
可以通過 init
方法裡注入的 Weex
上掛載的 Weex.utils
獲取到 utils 的方法:
function init (Weex) {
// 通過注入的 Weex 取得掛載的 utils 物件.
const utils = Weex.utils
// ...
}
小結
本文接著前篇《weex-html5 擴充套件開發指引》的話題,介紹了 weex-html5 裡的元件基類,執行流程,生命週期,螢幕適配,元件管理以及元件定義過程中的一些最佳實踐和注意事項等。通過這些介紹相信大家對文章開頭提出的幾個問題已經有了答案。對於 weex-html5 元件擴充套件還有什麼問題或建議歡迎與我交流。
相關文章
- React 進階之高階元件React元件
- React 進階(三) 高階元件React元件
- react進階系列:高階元件詳解(三)React元件
- React 進階二-元件最佳實踐React元件
- flutter的進階之路之常用元件Flutter元件
- 深入剖析Vue原始碼 - 元件進階Vue原始碼元件
- Vue 3 進階用法:非同步元件Vue非同步元件
- 【面試進階】React元件設計模式(一)面試React元件設計模式
- Netty進階內部元件詳解Netty元件
- 七進七出React高階元件React元件
- OpenStack入門之各元件解析(進階)元件
- react進階元件之Render Props小結React元件
- Vue2.0進階元件篇2 解析餓了麼(spinner元件)Vue元件
- Vue2.0 進階元件篇 5 解析 vux(無逢 marquee 元件)Vue元件UX
- Vue2.0 進階元件篇 3 值得一看的(Toast 元件)Vue元件AST
- React 進階之選擇合適的元件型別React元件型別
- 高階元件元件
- ASP.NET進階:認清控制元件 之 Button (轉)ASP.NET控制元件
- Vue2.0進階元件篇1 教你秒擼(簡訊倒數計時元件)Vue元件
- Vue2.0進階元件篇4 突如其來(時間倒數計時元件)Vue元件
- React高階指南之高階元件React元件
- React高階元件React元件
- React 高階元件React元件
- CSS高階進階CSS
- flutter的進階之路之Material Design與IOS風格元件FlutterMaterial DesigniOS元件
- WPF進階技巧和實戰03-控制元件(3-文字控制元件及列表控制元件)控制元件
- 從高階函式--->高階元件函式元件
- Vue 進階必學之高階元件 HOC(保姆式教學,衝擊20k必備)Vue元件
- 高階前端進階(五)前端
- 高階前端進階(七)前端
- 高階前端進階(三)前端
- Redux 進階Redux
- ElasticSearch 進階Elasticsearch
- HBase進階
- SQL進階SQL
- JavaScript進階JavaScript
- css進階CSS
- Mongodb進階MongoDB