摘要:最近數(shù)月一直投身于的開源工作中,完成了大大小小多個組件,在組件化開發(fā)中積累了不少經(jīng)驗。在開發(fā)全局提示組件通知提醒組件對話框組件時,內(nèi)部都是使用來渲染,但卻是來隱式地創(chuàng)建這些實例,這樣我們就可以像標(biāo)題這樣使用,但其內(nèi)部還是通過來管理。
寫在前面最近數(shù)月一直投身于 iView 的開源工作中,完成了大大小小 30 多個 UI 組件,在 Vue 組件化開發(fā)中積累了不少經(jīng)驗。其中也有很多帶有技巧性和黑科技的組件,這些特性有的是 Vue 文檔中提到但卻容易被忽略的,有的更是沒有寫在文檔里,今天就說說 Vue 組件的高級玩法。
本文所講內(nèi)容大多在 iView 項目中使用,大家可以前往關(guān)注,并結(jié)合源代碼來研究其中的奧妙。項目地址:
https://github.com/iview/iview
遞歸組件
自定義組件使用 v-model
使用$compile()在指定上下文中手動編譯組件
內(nèi)聯(lián)模板inline-template
隱式創(chuàng)建 Vue 實例
遞歸組件遞歸組件在文檔中有介紹,只要給組件指定一個 name字段,就可以在該組件遞歸地調(diào)用自己,例如:
var iview = Vue.extend({ name: "iview", template: "" + // 遞歸地調(diào)用它自己 "" })" + "
這種用法在業(yè)務(wù)中并不常見,在 iView 的級聯(lián)選擇組件中使用了該特性
(https://github.com/iview/iview/tree/master/src/components/cascader)
效果如下圖所示:
圖中每一列是一個組件(caspanel.vue),一開始想到用 v-for來渲染列表,但后面發(fā)現(xiàn)擴(kuò)展性極低,而且隨著功能的豐富,實現(xiàn)起來很困難,處理的邏輯很多,于是改寫成了遞歸組件:
props 比較多,可以忽略,但其中關(guān)鍵的兩個是data和 sublist,即當(dāng)前列數(shù)據(jù)和子集的數(shù)據(jù),因為預(yù)先不知道有多少下級,所以只需傳遞下級數(shù)據(jù)給組件本身,如果為空時,遞歸就結(jié)束了,Vue 這樣設(shè)計的確很精妙。
注:該方法在 Vue 1.x 和 2.x 中都支持。
我們知道,v-model是在表單類元素上進(jìn)行雙向綁定時使用的,比如:
{{ data }}
這時data就是雙向綁定的,輸入的內(nèi)容會實時顯示在頁面上。在 Vue 1.x 中,自定義組件可以使用 props 的.sync雙向綁定,比如:
在 Vue 2.x 中,可以直接在自定義組件上使用 v-model了,比如:
在組件my-component中,通過this.$emit("input")就可以改變data的值了。
雖然 Vue 1.x 中無法這樣使用,但是如果你的組件的模板外層是 input、select、textarea等支持綁定 v-model 特性的元素,也是可以使用的,比如 my-component 的代碼是:
那也可以使用上面2.x的寫法。
使用$compile()在指定上下文中手動編譯組件注:該方法是在 Vue 1.x 中的使用介紹,官方文檔并沒有給出該方法的任何說明,不可過多依賴此方法。
使用$compile()方法,可以在任何一個指定的上下文(Vue實例)上手動編譯組件,該方法在 iView 新發(fā)布的表格組件 Table 中有使用:
https://github.com/iview/iview/tree/master/src/components/table/cell.vue
由于表格的列配置是通過一個 Object 傳入 props 的,因此不能像 slot 那樣自動編譯帶有 Vue 代碼的部分,因為傳入的都是字符串,比如:
{ render (row) { return `${row.name} ` } }
render函數(shù)最終返回一個字符串,里面含有一個自定義組件 i-button,如果直接用{{{ }}}顯示,i-button 是不會被編譯的,那為了實現(xiàn)在單元格內(nèi)支持渲染自定義組件,就用到了$compile()方法。
比如我們在組件的父級編譯:
// 代碼片段 const template = this.render(this.row); // 通過上面的render函數(shù)得到字符串 const div = document.createElement("div"); div.innerHTML = template; this.$parent.$compile(div); // 在父級上下文編譯組件 this.$el.appendChild(cell); // 將編譯后的html插入當(dāng)前組件
這樣一來, i-button就被編譯了。
在某些時候使用$compile()確實能帶來益處,不過也會遇到很多問題值得思考:
這樣編譯容易把作用域搞混,所以要知道是在哪個Vue實例上編譯的;
手動編譯后,也需要在合適的時候使用$destroy()手動銷毀;
有時候容易重復(fù)編譯,所以要記得保存當(dāng)前編譯實例的id,這里可以通過 Vue 組件的_uid來唯一標(biāo)識(每個Vue實例都會有一個遞增的id,可以通過this._uid獲取)
另外,Vue 1.x 文檔也有提到另一個$mount()方法,可以實現(xiàn)類似的效果,在 Vue 2.x 文檔中,有 Vue.compile()方法,用于在render函數(shù)中編譯模板字符串,讀者可以結(jié)合來看。
內(nèi)聯(lián)模板inline-template內(nèi)聯(lián)模板并不是什么新鮮東西,文檔中也有說明,只是平時幾乎用不到,所以也容易忽略。簡短解說,就是把組件的 slot 當(dāng)做這個組件的模板來使用,這樣更為靈活:
{{ data }}
因為使用了 inline-template 內(nèi)聯(lián)模板,所以子組件不需要來聲明模板,這時它的模板直接是從 slot 來的{{ data }},而這個 data 所在的上下文,是子組件的,并不是父組件的,所以,在使用內(nèi)聯(lián)模板時,最容易產(chǎn)生的誤區(qū)就是混淆作用域。
隱式創(chuàng)建 Vue 實例在 webpack 中,我們都是用 .vue 單文件的模式來開發(fā),每個文件即一個組件,在需要的地方通過 components: {}來使用組件。
比如我們需要一個提示框組件,可能會在父級中這樣寫:
這是提示標(biāo)題
這樣寫沒有任何問題,但從使用角度想,我們其實并不期望這樣來用,反而原生的window.alert("這是提示標(biāo)題")這樣使用起來更靈活,那這時很多人可能就用原生 JS 拼字符串寫一個函數(shù)了,這也沒問題,不過如果你的提示框組件比較復(fù)雜,而且多處復(fù)用,這種方法還是不友好的,體現(xiàn)不到 Vue 的價值。
iView 在開發(fā)全局提示組件(Message)、通知提醒組件(Notice)、對話框組件(Modal)時,內(nèi)部都是使用 Vue 來渲染,但卻是 JS 來隱式地創(chuàng)建這些實例,這樣我們就可以像Message.info("標(biāo)題")這樣使用,但其內(nèi)部還是通過 Vue 來管理。相關(guān)代碼地址:
https://github.com/iview/iview/tree/master/src/components/base/notification
下面我們來看一下具體實現(xiàn):
上圖是最終效果圖,這部分 .vue 代碼比較簡單,相信大家都能寫出這樣一個組件來,所以直接說創(chuàng)建實例的部分,先看下核心代碼:
import Notification from "./notification.vue"; import Vue from "vue"; import { camelcaseToHyphen } from "../../../utils/assist"; Notification.newInstance = properties => { const _props = properties || {}; let props = ""; Object.keys(_props).forEach(prop => { props += " :" + camelcaseToHyphen(prop) + "=" + prop; }); const div = document.createElement("div"); div.innerHTML = ``; document.body.appendChild(div); const notification = new Vue({ el: div, data: _props, components: { Notification } }).$children[0]; return { notice (noticeProps) { notification.add(noticeProps); }, remove (key) { notification.close(key); }, component: notification, destroy () { document.body.removeChild(div); } } }; export default Notification;
與上文介紹的$compile()不同的是,這種方法是在全局(body)直接使用 new Vue創(chuàng)建一個 Vue 實例,我們只需要在入口處對外暴露幾個 API 即可:
import Notification from "../base/notification"; const prefixCls = "ivu-message"; const iconPrefixCls = "ivu-icon"; const prefixKey = "ivu_message_key_"; let defaultDuration = 1.5; let top; let messageInstance; let key = 1; const iconTypes = { "info": "information-circled", "success": "checkmark-circled", "warning": "android-alert", "error": "close-circled", "loading": "load-c" }; function getMessageInstance () { messageInstance = messageInstance || Notification.newInstance({ prefixCls: prefixCls, style: { top: `${top}px` } }); return messageInstance; } function notice (content, duration = defaultDuration, type, onClose) { if (!onClose) { onClose = function () { } } const iconType = iconTypes[type]; // if loading const loadCls = type === "loading" ? " ivu-load-loop" : ""; let instance = getMessageInstance(); instance.notice({ key: `${prefixKey}${key}`, duration: duration, style: {}, transitionName: "move-up", content: `${content}`, onClose: onClose }); // 用于手動消除 return (function () { let target = key++; return function () { instance.remove(`${prefixKey}${target}`); } })(); } export default { info (content, duration, onClose) { return notice(content, duration, "info", onClose); }, success (content, duration, onClose) { return notice(content, duration, "success", onClose); }, warning (content, duration, onClose) { return notice(content, duration, "warning", onClose); }, error (content, duration, onClose) { return notice(content, duration, "error", onClose); }, loading (content, duration, onClose) { return notice(content, duration, "loading", onClose); }, config (options) { if (options.top) { top = options.top; } if (options.duration) { defaultDuration = options.duration; } }, destroy () { let instance = getMessageInstance(); messageInstance = null; instance.destroy(); } }
到這里組件已經(jīng)可以通過Message.info()直接調(diào)用了,不過我們還可以在 Vue 上進(jìn)行擴(kuò)展:
Vue.prototype.$Message = Message;
這樣我們可以直接用this.$Message.info()來調(diào)用,就不用 import Message 了。
Vue 組件開發(fā)中有很多有意思的技巧,用好了會減少很多不必要的邏輯,用不好反而還弄巧成拙。在開發(fā)一個較復(fù)雜的組件時,一定要先對技術(shù)方案進(jìn)行調(diào)研和設(shè)計,然后再編碼。
iView 還有很多開發(fā)技巧和有意思的代碼,后面有時間我們再繼續(xù)探討吧,最近發(fā)布的幾個版本都有較大的更新,希望大家可以關(guān)注和推廣 iView ?:
https://github.com/iview/iview
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/91261.html
摘要:最近數(shù)月一直投身于的開源工作中,完成了大大小小多個組件,在組件化開發(fā)中積累了不少經(jīng)驗。在開發(fā)全局提示組件通知提醒組件對話框組件時,內(nèi)部都是使用來渲染,但卻是來隱式地創(chuàng)建這些實例,這樣我們就可以像標(biāo)題這樣使用,但其內(nèi)部還是通過來管理。 最近數(shù)月一直投身于 iView 的開源工作中,完成了大大小小 30 多個 UI 組件,在 Vue 組件化開發(fā)中積累了不少經(jīng)驗。其中也有很多帶有技巧性和黑科...
摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時候,會進(jìn)行總結(jié)或者分享會議那么今天我就把看過的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時候,會進(jìn)行總結(jié)或者分享會議!那么今天我就...
摘要:歡迎關(guān)注,專注數(shù)據(jù)分析數(shù)據(jù)挖掘好玩工具本文介紹一些我們可能很少聽過,但是對于特定問題或者特定任務(wù)來說,卻非常實用的工具包,文末提供技術(shù)交流群,歡迎大家一起學(xué)習(xí)討論。 ...
摘要:中還有大量的小工具,讓你的工作更有效率。快速共享服務(wù)器是內(nèi)置的服務(wù)器,使用端口和協(xié)議共享。安裝解壓縮常用操作是著名的包管理工具,在開發(fā)中必不可少。安裝示例最后介紹的示一個強(qiáng)大的工具。 ...
閱讀 1090·2021-11-15 18:00
閱讀 2802·2021-09-22 15:18
閱讀 1964·2021-09-04 16:45
閱讀 750·2019-08-30 15:55
閱讀 3853·2019-08-30 13:10
閱讀 1331·2019-08-30 11:06
閱讀 1984·2019-08-29 12:51
閱讀 2294·2019-08-26 13:55