自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

拋開(kāi) Vue、React、JQuery 這類第三方j(luò)s,我們?cè)撛趺磳懘a?

開(kāi)發(fā) 前端
這里使用VueJS同樣的數(shù)據(jù)綁定方式,但是由于數(shù)據(jù)對(duì)象屬性只能有一個(gè) set 函數(shù),所以建立了一個(gè)監(jiān)聽(tīng)隊(duì)列來(lái)進(jìn)行處理不同元素的數(shù)據(jù)綁定,這種隊(duì)列遍歷的方式和AngularJS臟值檢測(cè)的機(jī)制有些類似,但是觸發(fā)機(jī)制不同、數(shù)組長(zhǎng)度更小。

[[251442]]

第三方j(luò)s的現(xiàn)狀

無(wú)論是新入行的小白還是有經(jīng)驗(yàn)的開(kāi)發(fā)者,前端圈里的人一定聽(tīng)過(guò)這類第三方j(luò)s的大名。

一方面是因?yàn)樗鼈儗?shí)在太火了:

  • 各種文章對(duì)框架進(jìn)行對(duì)比、源碼解析以。
  • GitHub 上 star 數(shù)量高速增長(zhǎng)。
  • 各種針對(duì)框架的培訓(xùn)課程層出不窮。
  • ……

另一方面是因?yàn)橛盟鼈冮_(kāi)發(fā)非常方便:

  • 利用腳手架工具幾行命令就可以快速搭建項(xiàng)目。
  • 減少大量的重復(fù)代碼,結(jié)構(gòu)更加清晰,可讀性強(qiáng)。
  • 有豐富的UI庫(kù)和插件庫(kù)。
  • ……

但是一則 GitHub 放棄使用 JQuery 的消息讓我開(kāi)始思考:

第三方j(luò)s除了帶來(lái)便利之外還有哪些副作用?

拋棄第三方j(luò)s我們還能寫出高效的代碼嗎?

第三方j(luò)s的副作用

雪球滾起來(lái)

如果現(xiàn)在讓你開(kāi)發(fā)一個(gè)項(xiàng)目,你會(huì)怎么做?

假設(shè)你熟悉的是React,那么用可以用create-react-app快速搭建一個(gè)項(xiàng)目。

  • 很好,react、react-dom、react-router-dom 已經(jīng)寫入了package.json,不過(guò)事情還沒(méi)完。
  • http請(qǐng)求怎么處理呢?引入axios吧。
  • 日期怎么處理?引入 moment 或 day 吧。
  • ……

要知道,這種“拿來(lái)主義”是會(huì)“上癮”的,所以第三方依賴就像一個(gè)滾動(dòng)的雪球,隨著開(kāi)發(fā)不斷增加,***所占體積越來(lái)越大。

如果用 webpack-bundle-analyzer 工具來(lái)分析項(xiàng)目的話,會(huì)發(fā)現(xiàn)項(xiàng)目代碼大部分體積都在node_modules目錄中,也就意味著都是第三方j(luò)s,典型的二八定律(80%的源代碼只占了編譯后體積的20%)。

類似下面這張圖:

于是不得不開(kāi)始優(yōu)化,比如治標(biāo)不治本的code split(代碼體積并沒(méi)有減小,只是拆分了),比如萬(wàn)試萬(wàn)難靈的tree shaking(你確定shaking之后的代碼都只有你真正依賴的代碼?),優(yōu)化效果有限不說(shuō),更糟糕的是依賴的捆綁。

比如ant-design的模塊的日期組件依賴了moment,那我們?cè)谑褂盟臅r(shí)候moment就被引入了。

而且我即使發(fā)現(xiàn)體積更小的dayjs可以基本取代moment的功能,也不敢引入,因?yàn)樘鎿Q它日期組件會(huì)出問(wèn)題,同時(shí)引入又增加了項(xiàng)目體積。

有些第三方j(luò)s被合稱之為“全家桶”,這種叫法讓我想起了現(xiàn)在PC端的一些工具軟件,本來(lái)你只想裝一個(gè)電腦管家,結(jié)果它不斷彈窗提示你電腦不安全,建議你安裝一個(gè)殺毒軟件,又提示你軟件很久沒(méi)更新,提示你安裝某某軟件管家…..

本來(lái)只想裝一個(gè),結(jié)果裝了全家。

工具馴化

如果你注意觀察,在這些第三方j(luò)s的使用者中,會(huì)看到這樣一些現(xiàn)象:

  • 排他。一些使用 MV* 框架的開(kāi)發(fā)者很喜歡站隊(duì)進(jìn)行討論,比如喜歡用 VueJS 的開(kāi)發(fā)者很可能會(huì)吐槽 ReactJS,喜歡 Angular 的開(kāi)發(fā)者會(huì)噴 VueJS。
  • 浮躁。一些經(jīng)驗(yàn)并不豐富的開(kāi)發(fā)者會(huì)覺(jué)得:使用JavaScript操作DOM多么低效,直接來(lái)個(gè)第三方j(luò)s雙向數(shù)據(jù)綁定好了。自己寫XMLHTTPRequest發(fā)送請(qǐng)求多么麻煩,來(lái)第三方j(luò)s直接調(diào)用好了。
  • 局限。一些面試者以為自己熟悉某種第三方j(luò)s之后就覺(jué)得自己技術(shù)不錯(cuò)(甚至很多時(shí)候這種“熟悉”還要打上引號(hào)),大有掌握了某種第三方j(luò)s就掌握了前端之意。

這些第三方j(luò)s本來(lái)是為了提升開(kāi)發(fā)效率的工具,卻不知不覺(jué)地把開(kāi)發(fā)者馴化了,讓其產(chǎn)生了依賴。

如果每次讓你開(kāi)發(fā)新項(xiàng)目,你不得不依賴第三方j(luò)s提供的腳手架來(lái)搭建項(xiàng)目,然后才能開(kāi)始寫代碼。

那么很可能你已經(jīng)形成工具思維,就像手里拿著錘子,是什么都是釘子,你處理問(wèn)答的方式,看問(wèn)題的角度很可能會(huì)受此局限。

同時(shí)也意味著你正在離底層原生編碼越來(lái)越遠(yuǎn),越不熟悉原生API,你就越只能依賴第三方j(luò)s,如此循環(huán)往復(fù)。

怎么打破這種狀況?

先推薦張?chǎng)涡竦囊黄恼隆恫黄撇涣⒌恼軐W(xué)與個(gè)人成長(zhǎng)》,當(dāng)然就是放棄它們。

這里需要注意的是,我所說(shuō)的放棄并不是所有項(xiàng)目都自己寫框架,這樣在效率上而言是做不到的。

更推薦的而是在一些時(shí)間相對(duì)充裕、影響(規(guī)模)不大的項(xiàng)目中進(jìn)行嘗試。

比如開(kāi)發(fā)某個(gè)公司內(nèi)部使用的小工具,或者頁(yè)面數(shù)量不多的時(shí)間不緊張(看個(gè)人開(kāi)發(fā)速度)的小項(xiàng)目。

用原生API進(jìn)行開(kāi)發(fā)的時(shí)候我們可以參考下面兩條建議。

理解精髓

雖然我們不使用任何第三方j(luò)s,但是其原理及實(shí)現(xiàn)我們是可以學(xué)習(xí),比如你知道實(shí)現(xiàn)數(shù)據(jù)綁定的方式有臟值檢測(cè)、以及Object.defineProperty,那么你在寫代碼的時(shí)候就可以使用它們,你會(huì)發(fā)現(xiàn)懂這些原理和真正使用起來(lái)還有不小的距離。

換個(gè)角度而言,這也可以進(jìn)一步加深我們對(duì)第三方j(luò)s的理解。

當(dāng)然我們的目的并不是為了再造一個(gè)山寨版的js,而是適當(dāng)?shù)亟Y(jié)合、刪減和優(yōu)化已有的技術(shù)和思想,為業(yè)務(wù)定制最合適的代碼。

文中提到的第三方j(luò)s受歡迎很重要的一個(gè)原因是因?yàn)閷?duì)DOM操作進(jìn)行了優(yōu)化甚至是隱藏。

JQuery號(hào)稱是DOM操作的利器,將DOM封裝成JQ對(duì)象并擴(kuò)展了API,而MV框架取代JQuery的原因是因?yàn)樵贒OM操作這條路上做得更絕,直接屏蔽了底層操作,將數(shù)據(jù)映射到模板上。

如果這些MV的思考方式還只是停留在DOM的層次上的話估計(jì)也無(wú)法發(fā)展到今天的規(guī)模。

因?yàn)槠帘蜠OM只是簡(jiǎn)化了代碼而已,要搭建大型項(xiàng)目還要考慮代碼組織的問(wèn)題,就是抽象和復(fù)用。

這些第三方j(luò)s選擇的方式就是“組件化”,把HTML、js和CSS封裝在一個(gè)具有獨(dú)立作用域的組件中,形成可復(fù)用的代碼單元。

下面我們通過(guò)不引入任何第三方j(luò)s的情況下來(lái)進(jìn)行實(shí)現(xiàn)。

無(wú)依賴實(shí)踐

web components

先來(lái)考慮組件化。

其實(shí)瀏覽器原生就支持組件化(web components),它由3個(gè)關(guān)鍵技術(shù)組成,我們先來(lái)快速了解一下。

Custom elements(自定義元素)

一組js API,允許自定義元素及其行為,然后可以在您的用戶界面中按照需要使用它們。

簡(jiǎn)單示例:

  1. // 定義組件類 
  2. class LoginForm extends HTMLElement { 
  3.   constructor() { 
  4.     super(); 
  5.     ... 
  6.   } 
  7. // 注冊(cè)組件 
  8. customElements.define('login-form', LoginForm); 
  9. <!-- 使用組件 --> 
  10. <login-form></login-form> 

Shadow DOM(影子DOM)

一組js API,創(chuàng)建一顆可見(jiàn)的DOM樹(shù),這棵樹(shù)會(huì)附著到某個(gè)DOM元素上。

這棵樹(shù)的根節(jié)點(diǎn)稱之為shadow root,只有通過(guò)shadow root 才可以訪問(wèn)內(nèi)部的shadow dom,并且外部的css樣式也不會(huì)影響到shadow dom上。

相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的作用域。

常見(jiàn)的shadow root可以通過(guò)瀏覽器的調(diào)試工具進(jìn)行查看:

簡(jiǎn)單示例:

  1. // 'open' 表示該shadow dom可以通過(guò)js 的函數(shù)進(jìn)行訪問(wèn) 
  2. const shadow = dom.attachShadow({mode: 'open'}) 
  3. // 操作shadow dom 
  4. shadow.appendChild(h1); 

HTML templates(HTML模板)

HTML模板技術(shù)包含兩個(gè)標(biāo)簽:和 。

當(dāng)需要在頁(yè)面上重復(fù)使用同一個(gè) DOM結(jié)構(gòu)時(shí),可以用 template 標(biāo)簽來(lái)包裹它們,然后進(jìn)行復(fù)用。

slot標(biāo)簽讓模板更加靈活,使得用戶可以自定義模板中的某些內(nèi)容。

簡(jiǎn)單示例如下:

  1. <!-- template的定義 --> 
  2. <template id="my-paragraph"
  3.   <p><slot>My paragraph</slot></p> 
  4. </template> 
  5. // template的使用 
  6. let template = document.getElementById('my-paragraph'); 
  7. let templateContent = template.content; 
  8. document.body.appendChild(templateContent); 
  9.  
  10. <!-- 使用slot --> 
  11. <my-paragraph> 
  12.   <span slot="my-text">Let's have some different text!</span> 
  13. </my-paragraph> 
  14. <!-- 渲染結(jié)果 --> 
  15. <p> 
  16.   <span slot="my-text">Let's have some different text!</span> 
  17. </p> 

MDN上還提供了一些簡(jiǎn)單的例子。這里來(lái)一個(gè)完整的例子:

  1. const str = ` 
  2.   <style> 
  3.     p { 
  4.       color: white; 
  5.       background-color: #666; 
  6.       padding: 5px; 
  7.     } 
  8.   </style> 
  9.   <p><slot name="my-text">My default text</slot></p> 
  10. class MyParagraph extends HTMLElement { 
  11.   constructor() { 
  12.     super(); 
  13.     const template = document.createElement('template'); 
  14.     template.innerHTML = str; 
  15.     const templateContent = template.content; 
  16.     this.attachShadow({mode: 'open'}).appendChild( 
  17.       templateContent.cloneNode(true
  18.     ); 
  19.   } 
  20. customElements.define('my-paragraph', MyParagraph); 

完整的組件

不過(guò)這樣的組件功能還太弱了,因?yàn)楹芏鄷r(shí)候組件之間是需要有交互的,比如父組件向子組件傳遞參數(shù),子組件調(diào)用父組件回調(diào)函數(shù)。

因?yàn)樗荋TML標(biāo)簽,所以很自然地想到通過(guò)屬性來(lái)傳遞。而恰好組件也有生命周期函數(shù)來(lái)監(jiān)聽(tīng)屬性的變化,看似***!

不過(guò)問(wèn)題又來(lái)了,首先是性能問(wèn)題,這樣會(huì)增加對(duì)dom的讀寫操作。其次是數(shù)據(jù)類型問(wèn)題,HTML標(biāo)簽上只能傳遞字符串這類簡(jiǎn)單的數(shù)據(jù),而對(duì)于對(duì)象、數(shù)組、函數(shù)等這類復(fù)雜的數(shù)據(jù)就無(wú)能為力了。

你很可能想到對(duì)它們進(jìn)行序列化和反序列化來(lái)實(shí)現(xiàn),一來(lái)是弄得頁(yè)面很不美觀(想象一個(gè)長(zhǎng)度為100的數(shù)組參數(shù)被序列化后的樣子)。二來(lái)是操作復(fù)雜,不停地序列化和反序列化既容易出錯(cuò)也增加性能消耗。三來(lái)是一些數(shù)據(jù)無(wú)法被序列化,比如正則表達(dá)式、日期對(duì)象等。

好在我們可以通過(guò)選擇器獲取DOM實(shí)例來(lái)傳遞參數(shù)。但是這樣的話就不可避免地操作DOM,這可不是個(gè)好的處理方式。

另一方面,就組件內(nèi)部而言,如果我們需要?jiǎng)討B(tài)地將一些數(shù)據(jù)顯示到頁(yè)面上也需要操作DOM。

組件內(nèi)部視圖與數(shù)據(jù)地通信

將數(shù)據(jù)映射到視圖我們可以采用數(shù)據(jù)綁定的形式來(lái)實(shí)現(xiàn),而視圖的變化影響到數(shù)據(jù)可以采用事件的綁定的形式。

數(shù)據(jù)綁定

怎么楊將視圖和數(shù)據(jù)建立綁定關(guān)系,通常的做法是通過(guò)特定的模板語(yǔ)法來(lái)實(shí)現(xiàn),比如說(shuō)使用指令。

例如用x-bind指令來(lái)將數(shù)據(jù)體蟲(chóng)到視圖的文本內(nèi)容中。

臟值檢測(cè)的機(jī)制在性能上有損耗我們不考慮,那么剩下的就是利用Object.defineProperty這種監(jiān)聽(tīng)屬性值變化的方式來(lái)實(shí)現(xiàn)。

同時(shí)需要注意的是,一個(gè)數(shù)據(jù)可以對(duì)應(yīng)多個(gè)視圖,所以不能直接監(jiān)聽(tīng),而是要建立一個(gè)隊(duì)列來(lái)處理。

整理一下實(shí)現(xiàn)思路:

  1. 通過(guò)選擇器找出帶有x-bind屬性的元素,以及該屬性的值,比如<div x-bind="text"></div>的屬性值是text。
  2. 建立一個(gè)監(jiān)聽(tīng)隊(duì)列dispatcher保存屬性值以及對(duì)應(yīng)元素的處理函數(shù)。比如上面的元素監(jiān)聽(tīng)的是text屬性,處理函數(shù)是this.textContent = value;
  3. 建立一個(gè)數(shù)據(jù)模型state,編寫對(duì)應(yīng)屬性的set函數(shù),當(dāng)值發(fā)生變化時(shí)執(zhí)行dispatcher中的函數(shù)。

示例代碼:

  1. // 指令選擇器以及對(duì)應(yīng)處理函數(shù) 
  2. const map = { 
  3.   'x-bind'(value) { 
  4.     this.textContent = undefined === value ? '' : value; 
  5.   } 
  6. }; 
  7. // 建立監(jiān)聽(tīng)隊(duì)列,監(jiān)聽(tīng)數(shù)據(jù)對(duì)象屬性值得變動(dòng),然后遍歷執(zhí)行函數(shù) 
  8. for (const p in map) { 
  9.   forEach(this.qsa(`[${p}]`), dom => { 
  10.     const property = attr(dom, p).split('.').shift(); 
  11.     this.dispatcher[property] = this.dispatcher[property] || []; 
  12.     const fn = map[p].bind(dom); 
  13.     fn(this.state[property]); 
  14.     this.dispatcher[property].push(fn); 
  15.   }); 
  16. for (const property in this.dispatcher) { 
  17.   defineProperty(property); 
  18. // 監(jiān)聽(tīng)數(shù)據(jù)對(duì)象屬性 
  19. const defineProperty = p => { 
  20.   const prefix = '_s_'
  21.   Object.defineProperty(this.state, p, { 
  22.     get: () => { 
  23.       return this[prefix + p]; 
  24.     }, 
  25.     set: value => { 
  26.       if(this[prefix + p] !== value) { 
  27.         this.dispatcher[p].forEach(fun => fun(value, this[prefix + p])); 
  28.         this[prefix + p] = value; 
  29.       } 
  30.     } 
  31.   }); 
  32. }; 

這里不是操作了DOM了嗎?

沒(méi)關(guān)系,我們可以把DOM操作放入基類中,那么對(duì)于業(yè)務(wù)組件就不再需要接觸DOM了。

小結(jié):

這里使用VueJS同樣的數(shù)據(jù)綁定方式,但是由于數(shù)據(jù)對(duì)象屬性只能有一個(gè) set 函數(shù),所以建立了一個(gè)監(jiān)聽(tīng)隊(duì)列來(lái)進(jìn)行處理不同元素的數(shù)據(jù)綁定,這種隊(duì)列遍歷的方式和AngularJS臟值檢測(cè)的機(jī)制有些類似,但是觸發(fā)機(jī)制不同、數(shù)組長(zhǎng)度更小。

事件綁定

事件的綁定思路比數(shù)據(jù)綁定更簡(jiǎn)單,直接在DOM元素上進(jìn)行監(jiān)聽(tīng)即可。

我們以click事件為例進(jìn)行綁定,創(chuàng)建一個(gè)事件綁定的指令,比如x-click。

實(shí)現(xiàn)思路:

  1. 利用DOM選擇器找到帶有x-click屬性的元素。
  2. 讀取x-click屬性值,這時(shí)候我們需要對(duì)屬性值進(jìn)行一下判斷,因?yàn)閷傩灾涤锌赡苁呛瘮?shù)名比如x-click=fn,有可能是函數(shù)調(diào)用x-click=fn(a, true)。
  3. 對(duì)于基礎(chǔ)數(shù)據(jù)類型進(jìn)行判斷,比如布爾值、字符串,并加入到調(diào)用參數(shù)列表中。
  4. 為DOM元素添加事件監(jiān)聽(tīng),當(dāng)事件觸發(fā)時(shí)調(diào)用對(duì)應(yīng)函數(shù),傳入?yún)?shù)。

示例代碼:

  1. const map = ['x-click']; 
  2. map.forEach(event => { 
  3.   forEach(this.qsa(`[${event}]`), dom => { 
  4.     // 獲取屬性值 
  5.     const property = attr(dom, event); 
  6.     // 獲取函數(shù)名 
  7.     const fnName = property.split('(')[0]; 
  8.     // 獲取函數(shù)參數(shù) 
  9.     const params = property.indexOf('(') > 0 ? property.replace(/.*\((.*)\)/, '$1').split(',') : []; 
  10.     let args = []; 
  11.     // 解析函數(shù)參數(shù) 
  12.     params.forEach(param => { 
  13.       const p = param.trim(); 
  14.       const str = p.replace(/^'(.*)'$/, '$1').replace(/^"(.*)"$/, '$1'); 
  15.       if (str !== p) { // string 
  16.         args.push(str); 
  17.       } else if (p === 'true' || p === 'false') { // boolean 
  18.         args.push(p === 'true'); 
  19.       } else if (!isNaN(p)) { 
  20.         args.push(p * 1); 
  21.       } else { 
  22.         args.push(this.state[p]); 
  23.       } 
  24.     }); 
  25.     // 監(jiān)聽(tīng)事件 
  26.     on(event.replace('x-'''), dom, e => { 
  27.       // 調(diào)用函數(shù)并傳入?yún)?shù) 
  28.       this[fnName](...params, e); 
  29.     }); 
  30.   }); 
  31. }); 

對(duì)于表單控件的雙向數(shù)據(jù)綁定也很容易,即在建立數(shù)據(jù)綁定修改value,然后建立事件綁定監(jiān)聽(tīng)input事件即可。

組件與組件之間的通信

解決完組件內(nèi)部的視圖與數(shù)據(jù)的映射問(wèn)題我們來(lái)著手解決組件之間的通信問(wèn)題。

組件需要提供一個(gè)屬性對(duì)象來(lái)接收參數(shù),我們?cè)O(shè)定為props。

父=>子,數(shù)據(jù)傳遞

父組件要將值傳入子組件的props屬性,需要獲取子組件的實(shí)例,然后修改props屬性。

這樣的話就不可避免的操作DOM,那么我們考慮將DOM操作法放在基類中進(jìn)行。

那么問(wèn)題來(lái)了,怎么找到哪些標(biāo)簽是子組件,子組件有哪些屬性是需要綁定的?

可以通過(guò)命名規(guī)范和選擇其來(lái)獲取嗎?比如組件名稱都以cmp-開(kāi)頭,選擇器支不支持暫且不說(shuō),這種要求既約束編碼命名,同時(shí)有沒(méi)有規(guī)范保證。

簡(jiǎn)單地說(shuō)就是沒(méi)有靜態(tài)檢測(cè)機(jī)制,如果有開(kāi)發(fā)者寫的組件不是以cmp-開(kāi)頭,運(yùn)行時(shí)發(fā)現(xiàn)數(shù)據(jù)傳遞失敗檢查起來(lái)會(huì)比較麻煩。

所以可以在另一個(gè)地方對(duì)組件名稱進(jìn)行采集,那就是注冊(cè)組件函數(shù)。

我們通過(guò)customElements.define函數(shù)來(lái)注冊(cè)組件,一種方式是直接對(duì)該函數(shù)進(jìn)行重載,在注冊(cè)組件的時(shí)候記錄組件名稱,但是實(shí)現(xiàn)有些難度,而且對(duì)原生API函數(shù)修改難以保證不會(huì)對(duì)其它代碼產(chǎn)生影響。

所以折中的方式是對(duì)齊封裝,然后利用封裝的函數(shù)進(jìn)行組件注冊(cè)。

這樣我們就可以記錄所有注冊(cè)的組件名了,然后創(chuàng)建實(shí)例來(lái)獲取對(duì)應(yīng)props我們就解決了上面提出的問(wèn)題。

同時(shí)在props對(duì)象的屬性上編寫set函數(shù)進(jìn)行監(jiān)聽(tīng)。

到了這一步還只完成了一半,因?yàn)槲覀冞€沒(méi)有把數(shù)據(jù)傳遞給子組件。

我們不要操作DOM的話那就只能利用已有的數(shù)據(jù)綁定機(jī)制了,將需要傳遞的屬性綁定到數(shù)據(jù)對(duì)象上。

梳理一下思路:

  1. 編寫子組件的時(shí)候建立props對(duì)象,并聲明需要被傳參的屬性, 比如this.props = {id: ''}。
  2. 編寫子組件的時(shí)候不通過(guò)原生customElements.define,而是使用封裝過(guò)的函數(shù),比如defineComponent來(lái)注冊(cè),這樣可以記錄組件名和對(duì)應(yīng)的props屬性。
  3. 父組件在使用子組件的時(shí)候進(jìn)行遍歷,找出子組件和對(duì)應(yīng)的props對(duì)象。
  4. 將子組件props對(duì)象的屬性綁定到父組件的數(shù)據(jù)對(duì)象state屬性上,這樣當(dāng)父組件state屬性值發(fā)生變化時(shí),會(huì)自動(dòng)修改子組件props屬性值。

示例代碼:

  1. const components = {}; 
  2. /** 
  3.  * 注冊(cè)組件函數(shù) 
  4.  * @param {string} 組件(標(biāo)簽)名 
  5.  * @param {class} 組件實(shí)現(xiàn)類 
  6.  */ 
  7. export const defineComponent = (name, componentClass) => { 
  8.   // 注冊(cè)組件 
  9.   customElements.define(name, componentClass); 
  10.   // 創(chuàng)建組件實(shí)例 
  11.   const cmp = document.createElement(name); 
  12.   // 存儲(chǔ)組件名以及對(duì)應(yīng)的props屬性 
  13.   components[name] = Object.getOwnPropertyNames(cmp.props) || []; 
  14. }; 
  15. // 注冊(cè)子組件 
  16. class ChildComponent extends Component { 
  17.   constructor() { 
  18.     // 通過(guò)基類來(lái)創(chuàng)建模板 
  19.     // 通過(guò)基類來(lái)監(jiān)聽(tīng)props 
  20.     super(template, { 
  21.       id: value => { 
  22.         // ... 
  23.       } 
  24.     }); 
  25.   } 
  26.  
  27. defineComponent('child-component', ChildComponent); 
  28.  
  29. <!-- 使用子組件 --> 
  30. <child-component id="myId"></child-component> 
  31.  
  32. // 注冊(cè)父組件 
  33. class ParentComponent extends Component { 
  34.   constructor() { 
  35.     super(template); 
  36.     this.state.myId = 'xxx'
  37.   } 

上面的代碼中有很多地方可以繼續(xù)優(yōu)化,具體查看文末示例代碼。

子=>父,回調(diào)函數(shù)

子組件的參數(shù)要傳回給父組件,可以采用回調(diào)函數(shù)的形式。

比較麻煩的時(shí)候調(diào)用函數(shù)時(shí)需要用到父組件的作用域。

可以將父組件的函數(shù)進(jìn)行作用域綁定然后傳入子組件props對(duì)象屬性,這樣子組件就可以正常調(diào)用和傳參了。

因?yàn)榛卣{(diào)函數(shù)操作方式和參數(shù)不一樣,參數(shù)是被動(dòng)接收,回調(diào)函數(shù)是主動(dòng)調(diào)用,所以需要在聲明時(shí)進(jìn)行標(biāo)注,比如參考AngularJS指令的scope對(duì)象屬性的聲明方式,用“&”符號(hào)來(lái)表示回調(diào)函數(shù)。

理清一下思路:

  1. 子組件類中聲明props的屬性為回調(diào)函數(shù),如 this.props = {onClick:'&'}。
  2. 父組件初始化時(shí),在模板上傳遞對(duì)應(yīng)屬性, 如。
  3. 根據(jù)子組件屬性值找到對(duì)應(yīng)的父組件函數(shù),然后將父組件函數(shù)綁定作用域并傳入。如childComponent.props.onClick = this.click.bind(this)。
  4. 子組件中調(diào)用父組件函數(shù), 如this.props.onClick(...)。

示例代碼:

  1. // 注冊(cè)子組件 
  2. class ChildComponent extends Component { 
  3.   constructor() { 
  4.     // 通過(guò)基類來(lái)聲明回調(diào)函數(shù)屬性 
  5.     super(template, { 
  6.       onClick: '&' 
  7.     }); 
  8.     ... 
  9.     this.props.onClick(...); 
  10.   } 
  11.  
  12. defineComponent('child-component', ChildComponent); 
  13.  
  14. <!-- 父組件中使用子組件 --> 
  15. <child-component on-click="click"></child-component> 
  16.  
  17. // 注冊(cè)父組件 
  18. class ParentComponent extends Component { 
  19.   constructor() { 
  20.     super(template); 
  21.   } 
  22.   // 事件傳遞放在基類中操作 
  23.   click(data) { 
  24.     ... 
  25.   } 

穿越組件層級(jí)的通信

有些組件需要子孫組件進(jìn)行通信,層層傳遞會(huì)編寫很多額外的代碼,所以我們可以通過(guò)總線模式來(lái)進(jìn)行操作。

即建立一個(gè)全局模塊,數(shù)據(jù)發(fā)送者發(fā)送消息和數(shù)據(jù),數(shù)據(jù)接收者進(jìn)行監(jiān)聽(tīng)。

示例代碼

  1. // bus.js 
  2. // 監(jiān)聽(tīng)隊(duì)列 
  3. const dispatcher = {}; 
  4. /**  
  5.  * 接收消息 
  6.  * name  
  7.  */ 
  8. export const on = (name, cb) => { 
  9.   dispatcher[name] = dispatcher[name] || []; 
  10.   const key = Math.random().toString(26).substring(2, 10); 
  11.   // 將監(jiān)聽(tīng)函數(shù)放入隊(duì)列并生成唯一key 
  12.   dispatcher[name].push({ 
  13.     key
  14.     fn: cb 
  15.   }); 
  16.   return key
  17. }; 
  18. // 發(fā)送消息 
  19. export const emit = function(name, data) { 
  20.   const dispatchers = dispatcher[name] || []; 
  21.   // 輪詢監(jiān)聽(tīng)隊(duì)列并調(diào)用函數(shù) 
  22.   dispatchers.forEach(dp => { 
  23.     dp.fn(data, this); 
  24.   }); 
  25. }; 
  26. // 取消監(jiān)聽(tīng) 
  27. export const un = (namekey) => { 
  28.   const list = dispatcher[name] || []; 
  29.   const index = list.findIndex(item => item.key === key); 
  30.   // 從監(jiān)聽(tīng)隊(duì)列中刪除監(jiān)聽(tīng)函數(shù) 
  31.   if(index > -1) { 
  32.     list.splice(index, 1); 
  33.     return true
  34.   } else { 
  35.     return false
  36.   } 
  37. }; 
  38.  
  39. // ancestor.js 
  40. import {onfrom './bus.js'
  41.  
  42. class AncestorComponent extends Component { 
  43.   constructor() { 
  44.     super(); 
  45.     on('finish', data => { 
  46.       //... 
  47.     })     
  48.   } 
  49.  
  50. // child.js 
  51. class ChildComponent extends Component { 
  52.   constructor() { 
  53.     super(); 
  54.     emit('finish', data); 
  55.   } 

總結(jié)

關(guān)于基類的詳細(xì)代碼可以參考文末的倉(cāng)庫(kù)地址,目前項(xiàng)目遵循的是按需添加原則,只實(shí)現(xiàn)了一些基礎(chǔ)的操作,并沒(méi)有把所有可能用到的指令寫完。

所以還不足以稱之為“框架”,只是給大家提供實(shí)現(xiàn)思路以及編寫原生代碼的信心。

責(zé)任編輯:武曉燕 來(lái)源: web學(xué)習(xí)社
相關(guān)推薦

2015-11-05 16:44:37

第三方登陸android源碼

2011-04-26 14:29:01

javascriptGA

2014-02-10 10:22:33

微信開(kāi)發(fā)者

2025-03-04 10:00:00

架構(gòu)接口k開(kāi)發(fā)

2014-07-23 08:55:42

iOSFMDB

2019-07-30 11:35:54

AndroidRetrofit庫(kù)

2019-09-03 18:31:19

第三方支付電商支付行業(yè)

2009-12-31 14:38:34

Silverlight

2016-10-21 14:09:10

2017-12-11 15:53:56

2022-05-23 13:50:20

開(kāi)發(fā)封裝

2010-08-02 23:20:24

2022-01-14 09:57:14

鴻蒙HarmonyOS應(yīng)用

2009-01-14 12:45:05

MSNIM蘋果

2014-07-22 10:56:45

Android Stu第三方類庫(kù)

2017-05-16 13:24:02

LinuxCentOS第三方倉(cāng)庫(kù)

2021-09-26 10:43:08

注冊(cè)Istio集成

2013-08-12 16:04:19

第三方移動(dòng)應(yīng)用

2010-05-25 11:09:31

SVN工具

2024-04-03 12:57:29

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)