<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端:你可能不知道的動(dòng)態(tài)組件玩法

          共 17876字,需瀏覽 36分鐘

           ·

          2021-08-05 19:27




          突破自我

          文章轉(zhuǎn)載自: https://juejin.cn/post/6992483283187531789?作者: 羽飛

          關(guān)注并將「趣談前端」設(shè)為星標(biāo)

          每早08:30按時(shí)推送技術(shù)干貨/優(yōu)秀開源/技術(shù)思維

          ○ 背景

          這篇是作者在公司做了活動(dòng)架構(gòu)升級(jí)后,產(chǎn)出的主文的前導(dǎo)篇,考慮到本文相對(duì)獨(dú)立,因此抽離出單獨(dú)成文。

          題目為動(dòng)態(tài)組件,但為了好理解可以叫做遠(yuǎn)程加載動(dòng)態(tài)組件,后面統(tǒng)一簡化稱為“遠(yuǎn)程組件”。

          具體是怎么玩呢?別著急,聽我慢慢道來,看完后會(huì)感慨Vue組件還能這么玩??,還會(huì)學(xué)會(huì)一個(gè)Stylelint插件,配有DEMO,以及隱藏在最后的彩蛋。

          作者曾所在我司廣告事業(yè)部,廣告承載方式是以刮刮卡、大轉(zhuǎn)盤等活動(dòng)頁進(jìn)行展示,然后用戶參與出廣告券彈層。

          旁白說:遠(yuǎn)程組件其實(shí)在可視化低代碼平臺(tái)也有類似應(yīng)用,而我們這里也是利用了類似思路實(shí)現(xiàn)解耦了活動(dòng)頁和券彈層。繼續(xù)主題...

          d507777f605f98ea2a307b1321965152.webpimage.png

          遺留系統(tǒng)早先版本是一個(gè)活動(dòng)就綁定一個(gè)彈層,1對(duì)1的綁定關(guān)系。

          7a15710c2f16e2a392af12c7af15c518.webpimage.png

          現(xiàn)在的場景是一個(gè)活動(dòng)可能出不同樣式的彈層,這得把綁定關(guān)系解除。我們需要多對(duì)多,就是一個(gè)活動(dòng)頁面可以對(duì)應(yīng)多個(gè)廣告券彈層,也可以一個(gè)廣告券彈層對(duì)應(yīng)多個(gè)活動(dòng)頁面。

          我們可以在本地預(yù)先寫好幾個(gè)彈層,根據(jù)條件選擇不同的彈層,可以滿足一個(gè)活動(dòng)對(duì)多個(gè)彈層。

          而我們的需求是讓活動(dòng)頁面對(duì)應(yīng)無數(shù)種彈層,而不是多種,所以不可能把所有彈層都寫在本地。因此怎么辦呢?

          9658ba0c3dec126be37d42aebc8fea38.webpimage.png

          因此我們要根據(jù)所需,然后通過判斷所需的彈層,遠(yuǎn)端返回對(duì)應(yīng)的代碼。其實(shí)就是我們主題要講到的遠(yuǎn)程組件

          講得容易,該怎么做呢?

          ○ 遠(yuǎn)程組件核心

          Pure版本

          如果是Pure JS、CSS組成的彈層,很自然的我們想到,通過動(dòng)態(tài)的插入JS腳本和CSS,就能組成一個(gè)彈層。因此把編譯好的JS、CSS文件可以存放在遠(yuǎn)端CDN。

          8d49ca37a946c83865c02d34d0e38c0a.webpimage.png

          看上圖,我們可以看到彈窗出來之前,瀏覽器把CSS、JS下載下來了,然后根據(jù)既定代碼拼裝成一個(gè)彈層。

          //?CSS插入
          <link?rel="stylesheet"?href="http://yun.xxx.com/xxx.css">

          //?JS的動(dòng)態(tài)插入

          <script?type="text/javascript">?var?oHead?=?document.querySelector('.modal-group');
          ??var?oScript?=?document.createElement('script');
          ??oScript.type?=?"text/javascript";
          ??oScript.src?=?"http://yun.xxx.com/xxx.js";
          ??oHead.appendChild(oScript);?</script>?

          通過上面可知,JS、CSS方式能實(shí)現(xiàn)Pure版本的遠(yuǎn)程組件,而在Vue環(huán)境下能實(shí)現(xiàn)嗎。如果按照Pure JS、CSS動(dòng)態(tài)插入到Vue活動(dòng)下,也是可以很粗糙的實(shí)現(xiàn)的。

          但有沒有更優(yōu)雅的方式呢?

          ad21a64d1cc0322c9aae2348126accba.webpimage.png

          Vue版本

          選型這篇不細(xì)討論了,后續(xù)的主篇會(huì)講為什么選擇Vue。

          上述是遺留系統(tǒng)的方式,如果我們要技術(shù)棧遷移到Vue,也需要對(duì)遠(yuǎn)程組件遷移,我們需要改造它。

          讓我們來回顧下Vue的一些概念。

          組件形式

          「對(duì)象組件」

          一個(gè)彈窗,其實(shí)我們可以通過一個(gè)Vue組件表示,我們想把這個(gè)組件放到CDN,直接下載這個(gè)文件,然后在瀏覽器環(huán)境運(yùn)行它可行嗎?我們來嘗試下。

          基于Vue官方文檔,我們可以把如下的選項(xiàng)對(duì)象傳入Vue,通過new Vue來創(chuàng)建一個(gè)組件。

          {
          ??mounted:?()?=>?{
          ???console.log('加載')
          ??},
          ??template:?"<div?v-bind:style=\"{?color:?'red',?fontSize:?'12'?+?'px'?}\">Home?component</div>"
          }?

          借助于包含編譯器的運(yùn)行時(shí)版本,我們可以處理字符串形式的Template。

          --?運(yùn)行時(shí)-編譯器-vs-只包含運(yùn)行時(shí)[1]

          如果你需要在客戶端編譯模板 (比如傳入一個(gè)字符串給Template選項(xiàng),或掛載到一個(gè)元素上并以其 DOM 內(nèi)部的 HTML 作為模板),就將需要加上編譯器,即完整版

          似乎找到了新世界的大門。

          d02053b7bacd83e9bb0fbe1f69b55752.webpimage.png

          我們確實(shí)是可以通過這種形式實(shí)現(xiàn)Template、Script、CSS了,但對(duì)于開發(fā)同學(xué),字符串形式的Template、內(nèi)嵌的CSS,開發(fā)體驗(yàn)不友好。

          ae62e806a5106d92dfe23d7c1aac84ca.webpimage.png

          「單文件組件」

          這個(gè)時(shí)候很自然地想到SFC - 單文件組件。

          文件擴(kuò)展名為.vue的**single-file components (單文件組件)**為以上所有問題提供了解決方法 -- Vue文檔。

          4d9139dbb7fed44e7e44b363d7f64651.webpimage.png

          但怎么樣才能讓一個(gè).vue組件從遠(yuǎn)端下載下來,然后在當(dāng)前活動(dòng)Vue環(huán)境下運(yùn)行呢?這是個(gè)問題,由于.vue文件瀏覽器是識(shí)別不了的,但.js文件是可以的。

          我們先想一下,.vue文件是最終被轉(zhuǎn)換成了什么?

          4b466b0f8d9313b9cf3f38a07b929ac7.webp(圖片來源:1.03-vue文件的轉(zhuǎn)換 - 簡書[2]

          通過轉(zhuǎn)換,實(shí)際變成了一個(gè)JS對(duì)象。所以怎么才能把.vue轉(zhuǎn)換成.js呢?

          有兩種方式,一種通過運(yùn)行時(shí)轉(zhuǎn)換,我們找到了http-vue-loader[3]。通過Ajax獲取內(nèi)容,解析Template、CSS、Script,輸出一個(gè)JS對(duì)象。

          823c2fb3378d2e96ca24154d9c746427.webpimage.png

          而考慮到性能和兼容性,我們選擇預(yù)編譯,通過CSS預(yù)處理器、HTML模版預(yù)編譯器。

          Vue的官方提供了vue-loader,它會(huì)解析文件,提取每個(gè)語言塊,如有必要會(huì)通過其它 loader 處理,最后將他們組裝成一個(gè) ES Module,它的默認(rèn)導(dǎo)出是一個(gè) Vue.js 組件選項(xiàng)的對(duì)象。這指的是什么意思呢?官方提供選項(xiàng)對(duì)象形式的組件[4]DEMO。

          有了理論支持,現(xiàn)在需要考慮下實(shí)踐啦,用什么編譯?

          7583c3ae38e574e3188f42d60e857e8a.webpimage.png

          怎么構(gòu)建

          由于webpack編譯后會(huì)帶了很多關(guān)于模塊化相關(guān)的無用代碼,所以一般小型的庫會(huì)選擇rollup,這里我們也選擇rollup。

          //?rollup.config.js
          import?vue?from?'rollup-plugin-vue'
          import?commonjs?from?'rollup-plugin-commonjs'

          export?default?{
          ??input:?'./skin/SkinDemo.vue',
          ??output:?{
          ????format:?'iife',
          ????file:?'./dist/rollup.js',
          ????name:?'MyComponent'
          ??},
          ??plugins:?[
          ????commonjs(),
          ????vue()
          ??]
          }?

          通過rollup-plugin-vue,我們可以把.vue文件轉(zhuǎn)成.js, rollup編譯輸出的iife形式j(luò)s。

          26a7456b15213c931e8b311a383c5f4e.webpimage.png

          可以看到script、style、template分別被處理成對(duì)應(yīng)的片段,通過整合計(jì)算,這些片段會(huì)生成一個(gè)JS對(duì)象,保存為.js文件。下圖就是一個(gè)組件選項(xiàng)的對(duì)象。

          2ca43a9b0338b401887c198d116b9f4f.webpimage.png

          可以通過項(xiàng)目:github.com/fly0o0/remo…[5],嘗試下rollup文件夾下的構(gòu)建,具體看README說明。

          我們已經(jīng)有了一個(gè) Vue.js 組件選項(xiàng)的對(duì)象,怎么去讓它掛載到對(duì)應(yīng)的Vue App上呢?

          d582ead6a55db5816b0c8bd86428cfa8.webpimage.png

          掛載方式

          回想之前通讀Vue入門文檔,遇到一個(gè)動(dòng)態(tài)組件的概念,但當(dāng)時(shí)并不太理解它的使用場景。acdb008369048e47557cd1c8cb25c993.webp

          動(dòng)態(tài)組件是可以不固定具體的組件,根據(jù)規(guī)則替換不同的組件。從文檔上看出,支持一個(gè)組件的選項(xiàng)對(duì)象。

          最終實(shí)現(xiàn)

          首先需要構(gòu)建.vue文件,然后通過Ajax或動(dòng)態(tài)Script去加載遠(yuǎn)端JS。由于Ajax會(huì)有跨域限制,所以這里我們選擇動(dòng)態(tài)Script形式去加載。

          而我們剛才使用Rollup導(dǎo)出的方式是把內(nèi)容掛載在一個(gè)全局變量上。那就知道了,通過動(dòng)態(tài)Script插入后,就有一個(gè)全局變量MyComponent,把它掛載在動(dòng)態(tài)組件,最終就能把組件顯示在頁面上了。

          具體怎么操作?欠缺哪些步驟,首先我們需要一個(gè)加載遠(yuǎn)程.js組件的函數(shù)。

          //?加載遠(yuǎn)程組件js

          function?cleanup(script){
          ??if?(script.parentNode)?script.parentNode.removeChild(script)
          ??script.onload?=?null
          ??script.onerror?=?null
          ??script?=?null
          }

          function?scriptLoad(url)?{
          ??const?target?=?document.getElementsByTagName('script')[0]?||?document.head

          ??let?script?=?document.createElement('script')
          ??script.src?=?url
          ??target.parentNode.insertBefore(script,?target)

          ??return?new?Promise((resolve,?reject)?=>?{
          ????script.onload?=?function?()?{
          ??????resolve()
          ??????cleanup(script)
          ????}
          ????script.onerror?=?function?()?{
          ??????reject(new?Error('script?load?failed'))
          ??????cleanup(script)
          ????}
          ??})
          }

          export?default?scriptLoad?

          然后把加載下來的組件,掛載在對(duì)應(yīng)的動(dòng)態(tài)組件上。

          <!--?掛載遠(yuǎn)程組件?-->

          <template>
          ??<component
          ????class="remote-test"
          ????:is="mode">
          ??</component>
          </template>

          <script>
          import?scriptLoad?from?"./scriptLoad"

          export?default?{
          ??name:?"Remote",
          ??data()?{
          ????return?{
          ??????mode:?"",
          ????};
          ??},
          ??mounted()?{
          ????this.mountCom(this.url)
          ??},
          ??methods:?{
          ????async?mountCom(url)?{
          ??????//?下載遠(yuǎn)程js
          ??????await?scriptLoad(url)

          ??????//?掛載在mode
          ??????this.mode?=?window.MyComponent

          ??????//?清除MyComponent
          ??????window.MyComponent?=?null
          ????},
          ??}
          }
          </script>?

          基本一個(gè)Vue的遠(yuǎn)程組件就實(shí)現(xiàn)了,但發(fā)現(xiàn)還存在一個(gè)問題。

          ea0b58dd709127758317e607b77d7786.webpimage.png

          全局變量MyComponent需要約定好,但要實(shí)現(xiàn)比較好的開發(fā)體驗(yàn)來說,應(yīng)該盡量減少約定。

          導(dǎo)出方式

          怎么解決呢?由于我們導(dǎo)出是使用的IIFE方式,其實(shí)Rollup還支持UMD方式,包含了Common JS和AMD兩種方式。

          我們通過配置Rollup支持UMD。

          //?rollup.config.js
          import?vue?from?'rollup-plugin-vue'
          import?commonjs?from?'rollup-plugin-commonjs'

          export?default?{
          ??input:?'./skin/SkinDemo.vue',
          ??output:?{
          ????format:?'umd',
          ????file:?'./dist/rollup.js',
          ????name:?'MyComponent'
          ??},
          ??plugins:?[
          ????commonjs(),
          ????vue()
          ??]
          }?

          可以看到構(gòu)建完畢后,支持三種方式導(dǎo)出。625f0464498fb2119bfac730bc84bb3d.webp

          我們可以模擬node環(huán)境,命名全局變量exports、module,就可以在module.exports變量上拿到導(dǎo)出的組件。afd3fb6c3dc907715499908c9700f147.webp

          具體實(shí)現(xiàn)核心代碼如下。

          <!--?掛載遠(yuǎn)程組件?-->

          <template>
          ??<component
          ????class="remote-test"
          ????:is="mode">
          ??</component>
          </template>

          <script>
          import?scriptLoad?from?"./scriptLoad"

          export?default?{
          ??name:?"Remote",
          ??data()?{
          ????return?{
          ??????mode:?"",
          ????};
          ??},
          ??mounted()?{
          ????this.mountCom(this.url)
          ??},
          ??methods:?{
          ????async?mountCom(url)?{
          ??????//?模擬node環(huán)境
          ??????window.module?=?{}
          ??????window.exports?=?{}

          ??????//?下載遠(yuǎn)程js
          ??????await?scriptLoad(url)

          ??????//?掛載在mode
          ??????this.mode?=?window.module.exports

          ??????//?清除
          ??????delete?window.module
          ??????delete?window.exports
          ????},
          ??}
          }
          </script>?

          終于搞定了Vue版本的遠(yuǎn)程組件加載的方式。

          6ad75e529034244f00da29b4edfc5857.webpimage.png

          接下來得想一想,怎么處理遠(yuǎn)程組件(彈層)的設(shè)計(jì)了。

          小結(jié)

          通過使用Vue動(dòng)態(tài)組件實(shí)現(xiàn)了遠(yuǎn)程組件功能,取代了老架構(gòu)。dd75a0e2c8ff7e06c0c0637f361d6b4c.webp

          可以通過以下地址去嘗試一下遠(yuǎn)程組件彈層,按照項(xiàng)目的README操作一下。會(huì)得到以下遠(yuǎn)程組件彈層。

          項(xiàng)目地址:github.com/fly0o0/remo…[6]

          d54fddd03234a5af29a7c18ebd8b63d8.webpimage.png○ 遠(yuǎn)程組件(彈層)設(shè)計(jì)

          遠(yuǎn)程組件已達(dá)成,這部分主要是對(duì)遠(yuǎn)程彈層組件的一些設(shè)計(jì)。

          對(duì)于遠(yuǎn)程單組件本身來說,只需要根據(jù)數(shù)據(jù)渲染視圖,根據(jù)用戶行為觸發(fā)業(yè)務(wù)邏輯,整個(gè)代碼邏輯是這樣的。

          需要考慮組件復(fù)用、組件通訊、組件封裝、樣式層級(jí)等方向。

          首先我們先看看組件復(fù)用。

          為了方便統(tǒng)一管理和減少冗余代碼,我們一般寫一些類似的組件會(huì)抽取一部分可以公共的組件,例如按鈕等。

          但遠(yuǎn)程單組件代碼和頁面端代碼是分離的啊(可以理解為兩個(gè)webpack入口打包出的產(chǎn)物),我們得想想公共組件需要放在哪里了。

          b838db028e01aabbb568cb4273d6bcde.webpimage.png

          組件復(fù)用

          現(xiàn)在可以發(fā)現(xiàn)有三種情況,我們利用枚舉法嘗試想一遍。

          打包 ??

          公共組件和遠(yuǎn)程組件打包一起?

          放在一起肯定不合適,不僅會(huì)引起遠(yuǎn)程組件變大,還不能讓其他遠(yuǎn)程組件復(fù)用。往下考慮再看看。

          32e29f83f93810c31e770157f626b472.webp

          公共組件單獨(dú)打包?

          遠(yuǎn)程組件、公共組件分別單獨(dú)打包,這樣也是不利的,由于遠(yuǎn)程組件抽離的公共組件少于5個(gè),而且代碼量較少,單獨(dú)作為一層打包,會(huì)多一個(gè)后置請(qǐng)求,影響遠(yuǎn)程組件的第一時(shí)間展示。

          繼續(xù)考慮再看看。3ce5f6fcbbf480f1f365c31c9d12e513.webp公共組件和頁面核心庫打包一起?

          把公共組件和頁面核心庫打包到一起,避免后面遠(yuǎn)程組件用到時(shí)候再加載,可以提升遠(yuǎn)程組件的展示速度。84ac0b507b68751386fa55b9cb435bdf.webp

          因此最終敲定選擇最后種,把公共組件和頁面核心庫打包在一起。

          如果把遠(yuǎn)程組件.js和公共組件分開了,那我們?cè)撛趺床拍苁褂霉步M件啊???

          0feff210ebc8c6ce54cb0a8578e06aa4.webpimage.png

          注冊(cè) ??

          回顧下Vue官方文檔,Vue.component它可以提供注冊(cè)組件的能力,然后在全局能引用到。我們來試試吧。

          公共組件例如按鈕、關(guān)閉等,需要通過以下途徑去注冊(cè)。

          一個(gè)按鈕組件

          //?本地頁面端(本地是相較于在遠(yuǎn)端CDN)

          <!--?按鈕組件?-->
          <template>
          ??<button?type="button"?class="btn"?@click="use">
          ??</button>
          </template>

          <script>
          export?default?{
          ??name:?'Button',
          ??inject:?['couponUseCallback'],
          ??methods:?{
          ????use()?{
          ??????this.couponUseCallback?&&?this.couponUseCallback()
          ????}
          ??}
          }
          </script>?

          一個(gè)關(guān)閉組件

          //?本地頁面端(本地是相較于在遠(yuǎn)端CDN)

          <!--?關(guān)閉組件?-->
          <template>
          ??<span @click="close"?class="close"></span>
          </template>

          <script>
          export?default?{
          ??name:?"CouponClose",
          ??inject:?["couponCloseCallback"],
          ??methods:?{
          ????close()?{
          ??????this.couponCloseCallback?&&?this.couponCloseCallback();
          ????},
          ??},
          };
          </script>

          <style?lang="less"?scoped>
          .close?{
          ??&.gg?{
          ????background-image:?url("http://yun.tuisnake.com/h5-mami/dist/close-gg.png")?!important;
          ????background-size:?100%?!important;
          ????width:?92px?!important;
          ????height:?60px?!important;
          ??}
          }
          </style>?

          通過Vue.component全局注冊(cè)公共組件,這樣在遠(yuǎn)程組件中我們就可以直接調(diào)用了。

          //?本地頁面端(本地是相較于在遠(yuǎn)端CDN)

          <script>
          ??Vue.component("CpButton",?Button);
          ??Vue.component("CpClose",?Close);
          </script>?

          解決了公共組件復(fù)用的問題,后面需要考慮下遠(yuǎn)程組件和頁面容器,還有不同類型的遠(yuǎn)程組件之間的通訊問題。

          899d4fff47999b9540506d9d5e872831.webpimage.png

          組件通訊

          可以把頁面容器理解為父親、遠(yuǎn)程組件理解為兒子,兩者存在父子組件跨級(jí)雙向通訊,這里的父子也包含了爺孫和爺爺孫的情況,因此非props可以支持。那怎么處理?

          可以通過在頁面核心庫中向遠(yuǎn)程組件 provide 自身,遠(yuǎn)程組件中 inject 活動(dòng)實(shí)例,實(shí)現(xiàn)事件的觸發(fā)及回調(diào)。

          那不同類型的遠(yuǎn)程組件之間怎么辦呢,使用Event Bus,可以利用頂層頁面實(shí)例作為事件中心,利用 on 和 emit 進(jìn)行溝通,降低不同類別遠(yuǎn)程組件之間的耦合度。

          22fb699df98eb67387b0736d608b0806.webpimage.png

          組件封裝

          現(xiàn)在有個(gè)組件封裝的問題,先看個(gè)例子,基本就大概有了解了。

          現(xiàn)有3個(gè)嵌套組件,如下圖。** **現(xiàn)在需要從頂層組件Main.vue給底層組件RealComponent的一個(gè)count賦值,然后監(jiān)聽RealComponent的input組件的事件,如果有改變通知Main.vue里的方法。怎么做呢?

          6ad97ba1f64ecd1708e5212284284bb4.webp

          跨層級(jí)通信,有多少種方案可以選擇?

          1. 我們使用vuex來進(jìn)行數(shù)據(jù)管理,對(duì)于這個(gè)需求過重。
          2. 自定義vue bus事件總線(如上面提到的),無明顯依賴關(guān)系的消息傳遞,如果傳遞組件所需的props不太合適。
          3. 通過props一層一層傳遞,但需要傳遞的事件和屬性較多,增加維護(hù)成本。

          而還有一種方式可以通過attrs和attrs和attrs和listeners,實(shí)現(xiàn)跨層級(jí)屬性和事件“透傳”。

          主組件

          //?Main.vue

          <template>
          <div>
          ??<h2>組件Main?數(shù)據(jù)項(xiàng):{{count}}</h2>
          ??<ComponentWrapper?@changeCount="changeCount"?:count="count">
          ??</ComponentWrapper>
          </div>
          </template>
          <script>
          import?ComponentWrapper?from?"./ComponentWrapper";
          export?default?{
          ??data()?{
          ????return?{
          ??????count:?100
          ????};
          ??},
          ??components:?{
          ????ComponentWrapper
          ??},
          ??methods:?{
          ????changeCount(val)?{
          ??????console.log('Top?count',?val)
          ??????this.count?=?val;
          ????}
          ??}
          };
          </script>?

          包裝用的組件

          有的時(shí)候我們?yōu)榱藢?duì)真實(shí)組件進(jìn)行一些功能增加,這時(shí)候就需要用到包裝組件(特別是對(duì)第三方組件庫進(jìn)行封裝的時(shí)候)。

          //?ComponentWrapper.vue

          <template>
          ??<div>
          ????<h3>組件包裹層</h3>
          ????<RealComponent?v-bind="$attrs"?v-on="$listeners"></RealComponent>
          ??</div>
          </template>
          <script>
          import?RealComponent?from?"./RealComponent";
          export?default?{
          ??inheritAttrs:?false,?//?默認(rèn)就是true
          ??components:?{
          ????RealComponent
          ??}
          };
          </script>?

          真正的組件

          //?RealComponent.vue

          <template>
          ??<div>
          ????<h3>真實(shí)組件</h3>
          ????<input?v-model="myCount"?@input="inputHanlder"?/>
          ??</div>
          </template>
          <script>
          export?default?{
          ??data()?{
          ????return?{
          ??????myCount:?0
          ????}
          ??},
          ??created()?{
          ????this.myCount?=?this.$attrs.count;??//?在組件Main中傳遞過來的屬性
          ????console.info(this.$attrs,?this.$listeners);
          ??},
          ??methods:?{
          ????inputHanlder()?{
          ??????console.log('Bottom?count',?this.myCount)
          ??????this.$emit("changeCount",?this.myCount);?//?在組件Main中傳遞過來的事件,通過emit調(diào)用頂層的事件
          ??????//?this.$listeners.changeCount(this.myCount)?//?或者通過回調(diào)的方式
          ????}
          ??}
          };
          </script>?

          從例子中回歸本文里來,我們要面對(duì)的場景是如下這樣。

          遠(yuǎn)程組件其實(shí)有兩層,一層是本地(頁面內(nèi)),一層是遠(yuǎn)端(CDN)。本地這層只是做封裝用的,可以理解為只是包裝了一層,沒有實(shí)際功能。這時(shí)候可以理解為本地這一層組件就是包裝層,包裝層主要做了導(dǎo)入遠(yuǎn)程組件的功能沒辦法去除,需要利用上面的特性去傳遞信息給遠(yuǎn)程組件。

          樣式層級(jí)

          遠(yuǎn)程組件在本文可以簡單理解為遠(yuǎn)端的彈層組件,公司業(yè)務(wù)又涉及到不同的彈層類別,每種彈層類別可能會(huì)重疊。

          約定z-index

          因此劃分 0~90 為劃分十層,后續(xù)可根據(jù)實(shí)際情況增加數(shù)值,設(shè)定各遠(yuǎn)程組件容器只能在規(guī)定層級(jí)內(nèi)指定 z-index。

          //?const.js
          const?FLOOR?=?{
          ??MAIN:?0,???//?主頁面容器
          ??COUPON_MODAL:?20,??//?廣告彈層
          ??OTHER_MODAL:?30,?//?其他彈層
          ??ERROR_MODAL:?90,
          ??...
          }?

          設(shè)置每種遠(yuǎn)程組件即彈層的包裹層。

          ?//?CouponModalWrapper.vue
          <script>
          <template>
          ??<div?class="modal-wrapper"?:style="{'z-index':?FLOOR.COUPON_MODAL}"[email protected]>
          ????<slot></slot>
          ??</div>
          </template>

          //?OtherModalWrapper.vue
          <template>
          ??<div?class="modal-wrapper"?:style="{'z-index':?FLOOR.OTHER_MODAL}"[email protected]>
          ????<slot></slot>
          ??</div>
          </template>

          //?這里只是為了表意簡單,實(shí)際上兩個(gè)Wrapper.vue可以合并?

          然后每類別各自引入對(duì)應(yīng)的彈層包裹層。

          //?每類別公共組件有一個(gè)

          //?CouponModal2.vue??
          <template>
          ??<CouponModalWrapper>
          ??...
          ??</CouponModalWrapper>
          </template>
          ??
          //?OtherModal2.vue??
          <template>
          ??<OtherModalWrapper>
          ??...
          ??</OtherModalWrapper>
          </template>?

          通過這種約定的方式,可以避免一些問題,但假如真的有人想搗亂怎么辦?

          9cdf6bacd0cc2e11e7ff717403304ae9.webpimage.png

          別著急,有辦法的。

          借助stylelint

          思路是這樣的,每類別的遠(yuǎn)程組件是單獨(dú)有對(duì)應(yīng)的主文件夾,可以為這個(gè)文件夾定義最高和最小可允許的z-index,那該怎么做呢?

          不知道大家有使用過自動(dòng)加-webkit等前綴的插件 - autoprefixer沒有,它其實(shí)是基于一款postcss工具做的。而我們經(jīng)常用作css校驗(yàn)格式的工具stylelint也是基于它開發(fā)的。

          這時(shí)候我們想到,能不能通過stylelint的能力,進(jìn)行約束呢,我們發(fā)現(xiàn)找了官方文檔并沒有我們想要的API。

          我們需要自己開發(fā)一個(gè)stylelint插件,來看看一個(gè)基本的stylelint插件的插件。

          03ea01f62cf64899cdf1084f77f7f96a.webp

          stylelint通過stylelint.createPlugin方法,接受一個(gè)函數(shù),返回一個(gè)函數(shù)。

          const?stylelint?=?require('stylelint');
          const?ruleName?=?'plugin/z-index-range-plugin';

          function?rule(options)?{
          ??//?options傳入的配置
          ??return?(cssRoot,?result)?=>?{
          ????//?cssRoot即為postcss對(duì)象
          ???};
          }

          module.exports?=?stylelint.createPlugin(
          ??ruleName,
          ??rule
          );?

          函數(shù)中可以拿到PostCSS對(duì)象,可以利用PostCSS對(duì)代碼進(jìn)行解析成AST、遍歷、修改、AST變代碼等操作。

          有一些我們可用的概念。

          • rule,選擇器,比如.class { z-index: 99 }。
          • decl,屬性,比如z-index: 99。

          我們需要檢查z-index的值,因此需要遍歷CSS檢查z-index。我們可以調(diào)用cssRoot.walkDecls對(duì)做遍歷:

          //?遍歷
          cssRoot.walkDecls((decl)?=>?{
          ??//?獲取屬性定義
          ??if?(decl)?{?
          ????//?...?
          ??}?
          });?

          前置基礎(chǔ)知識(shí)差不多夠用了。

          77bbed422f8c659111cc006088f28681.webpimage.png

          假如我們要檢測一個(gè)兩個(gè)文件夾下的.css文件的z-index是否合乎規(guī)矩。

          我們?cè)O(shè)置好兩個(gè)模塊stylelint配置文件下的z-index范圍。

          這里我們可以看到stylelint配置文件,兩個(gè)css文件。

          ├──?.stylelintrc.js
          ├──?module1
          │???└──?index.css
          ├──?module2
          │???└──?index2.css?

          stylelint配置文件

          //?.stylelintrc.js
          module.exports?=?{
          ??"extends":?"stylelint-config-standard",
          ??//?自定義插件
          ??"plugins":?["./plugin.js"],
          ??"rules":?{
          ????//?自定義插件的規(guī)則
          ????"plugin/z-index-range-plugin":?{
          ??????//?設(shè)置的范圍,保證各模塊不重復(fù)
          ??????"module1":?[100,?199],
          ??????"module2":?[200,?299]
          ????}
          ??}
          }?

          CSS測試文件

          /*?module1/index.css?*/
          .classA?{
          ??color:?red;
          ??width:?99px;
          ??height:?100px;
          ??z-index:?99;
          }

          /*?module2/index.css?*/
          .classB?{
          ????color:?red;
          ????width:?99px;
          ????height:?100px;
          ????z-index:?200;
          }?

          我們要達(dá)到的目的是,運(yùn)行如下命令,會(huì)讓module1/index.css報(bào)錯(cuò),說z-index小于預(yù)期。

          npx?stylelint?"*/index.css"?

          于是乎我們完成了如下代碼,達(dá)成了預(yù)期目的。

          const?stylelint?=?require('stylelint');
          const?ruleName?=?'plugin/z-index-range-plugin';

          function?ruleFn(options)?{
          ??return?function?(cssRoot,?result)?{

          ????cssRoot.walkDecls('z-index',?function?(decl)?{
          ??????//?遍歷路徑
          ??????const?path?=?decl.source.input.file
          ??????//?提取文件路徑里的模塊信息
          ??????const?match?=?path.match(/module\d/)
          ??????//?獲取文件夾
          ??????const?folder?=?match?.[0]
          ??????//?獲取z-index的值
          ??????const?value?=?Number(decl.value);
          ??????//?獲取設(shè)定的最大值、最小值
          ??????const?params?=?{
          ????????min:?options?.[folder]?.[0],
          ????????max:?options?.[folder]?.[1],
          ??????}

          ??????if?(params.max?&&?Math.abs(value)?>?params.max)?{
          ????????//?調(diào)用?stylelint?提供的report方法給出報(bào)錯(cuò)提示
          ????????stylelint.utils.report({
          ??????????ruleName,
          ??????????result,
          ??????????node:?decl,
          ??????????message:?`Expected?z-index?to?have?maximum?value?of?${params.max}.`
          ????????});
          ??????}

          ??????if?(params.min?&&?Math.abs(value)?<?params.min)?{
          ????????//?調(diào)用?stylelint?提供的report方法給出報(bào)錯(cuò)提示
          ????????stylelint.utils.report({
          ??????????ruleName,
          ??????????result,
          ??????????node:?decl,
          ??????????message:?`Expected?z-index?to?have?minimum?value?of?${params.min}.`
          ????????});
          ??????}
          ????});
          ??};
          }

          module.exports?=?stylelint.createPlugin(
          ??ruleName,
          ??ruleFn
          );

          module.exports.ruleName?=?ruleName;?

          可以嘗試項(xiàng)目:github.com/fly0o0/styl…[7],試一試感受一下??。

          這樣基本一個(gè)遠(yuǎn)程彈層的設(shè)計(jì)就完成了。

          但還是遇到了些問題,艱難??。

          1b2d6d4050451db8a90507bf4fd2492e.webpimage.png○ 遇到的問題

          我們興沖沖的打算發(fā)上線了,結(jié)果報(bào)錯(cuò)了??。報(bào)的錯(cuò)是webpackJsonp不是一個(gè)function。

          不要慌,先吃個(gè)瓜鎮(zhèn)靜鎮(zhèn)靜。webpackJsonp是做什么的呢?

          異步加載的例子

          先看下以下例子,通過import的按需異步加載特性加載了test.js,以下例子基于Webpack3構(gòu)建。

          //?異步加載?test.js
          import('./test').then((say)?=>?{
          ??say();
          });?

          然后生成了異步加載文件 0.bundle.js。

          //?異步加載的文件,0.bundle.js
          webpackJsonp(
          ??//?在其它文件中存放著的模塊的?ID
          ??[0],
          ??//?本文件所包含的模塊
          ??[
          ????//?test.js?所對(duì)應(yīng)的模塊
          ????(function?(module,?exports)?{
          ??????function?;(content)?{
          ????????console.log('i?am?test')
          ??????}

          ??????module.exports?=?say;
          ????})
          ??]
          );?

          和執(zhí)行入口文件 bundle.js。

          //?執(zhí)行入口文件,bundle.js
          (function?(modules)?{
          ??/***
          ???* webpackJsonp 用于從異步加載的文件中安裝模塊。
          ???*
          ???*/
          ??window["webpackJsonp"]?=?function?webpackJsonpCallback(chunkIds,?moreModules,?executeModules)?{
          ????var?moduleId,?chunkId,?i?=?0,?resolves?=?[],?result;
          ????for?(;?i?<?chunkIds.length;?i++)?{
          ??????chunkId?=?chunkIds[i];
          ??????if?(installedChunks[chunkId])?{
          ????????resolves.push(installedChunks[chunkId][0]);
          ??????}
          ??????installedChunks[chunkId]?=?0;
          ????}
          ????for?(moduleId?in?moreModules)?{
          ??????if?(Object.prototype.hasOwnProperty.call(moreModules,?moduleId))?{
          ????????modules[moduleId]?=?moreModules[moduleId];
          ??????}
          ????}
          ????while?(resolves.length)?{
          ??????resolves.shift()();
          ????}
          ??};

          ??//?模擬?require?語句
          ??function?__webpack_require__(moduleId)?{
          ??}

          ??/**
          ???*?用于加載被分割出去的,需要異步加載的?Chunk?對(duì)應(yīng)的文件
          ???*/
          ??__webpack_require__.e?=?function?requireEnsure(chunkId)?{
          ????//?...?省略代碼
          ????return?promise;
          ??};

          ??return?__webpack_require__(__webpack_require__.s?=?0);
          })
          (
          ??[
          ????//?main.js?對(duì)應(yīng)的模塊
          ????(function?(module,?exports,?__webpack_require__)?{
          ??????//?通過?__webpack_require__.e?去異步加載?show.js?對(duì)應(yīng)的?Chunk
          ??????__webpack_require__.e(0).then(__webpack_require__.bind(null,?1)).then((show)?=>?{
          ????????//?執(zhí)行?show?函數(shù)
          ????????show('Webpack');
          ??????});
          ????})
          ??]
          );?

          可以看出webpackJsonp的作用是加載異步模塊文件。但為什么會(huì)報(bào)webpackJsonp不是一個(gè)函數(shù)呢?

          開始排查問題

          我們開始檢查構(gòu)建出的源碼,發(fā)現(xiàn)我們的webpackJsonp并不是一個(gè)函數(shù),而是一個(gè)數(shù)組(現(xiàn)已知Webpack4,當(dāng)時(shí)排查時(shí)候不知道)。

          我們發(fā)現(xiàn)異步文件加載的時(shí)候確實(shí)是變成了數(shù)組,通過push去增加一個(gè)異步模塊到系統(tǒng)里。

          //?異步加載的文件

          (window["webpackJsonp"]?=?window["webpackJsonp"]?||?[]).push([[/*?chunk?id?*/?0],?{
          ??"./src/async.js":?(function(module,?__webpack_exports__,?__webpack_require__)?{

          ?//...

          }))?

          且在執(zhí)行入口文件也發(fā)現(xiàn)了webpackJsonp被定義為了數(shù)組。

          //?執(zhí)行入口文件,bundle.js中的核心代碼?

          var?jsonpArray?=?window["webpackJsonp"]?=?window["webpackJsonp"]?||?[];
          var?oldJsonpFunction?=?jsonpArray.push.bind(jsonpArray);
          jsonpArray.push?=?webpackJsonpCallback;
          jsonpArray?=?jsonpArray.slice();
          for?(var?i?=?0;?i?<?jsonpArray.length;?i++)?webpackJsonpCallback(jsonpArray[i]);
          var?parentJsonpFunction?=?oldJsonpFunction;?

          確實(shí)我們構(gòu)建出的源碼的webpackJsonp是一個(gè)數(shù)組,確實(shí)不是一個(gè)函數(shù)了,感覺找到了一點(diǎn)線索。但為什么會(huì)webpackJsonp會(huì)函數(shù)形式去使用呢?

          我們懷疑報(bào)錯(cuò)處有問題,開始排查報(bào)錯(cuò)處,發(fā)現(xiàn)對(duì)應(yīng)的文件確實(shí)是用webpackJsonp當(dāng)作函數(shù)去調(diào)用的,這是什么情況????

          這時(shí)我們注意到報(bào)錯(cuò)的都是老架構(gòu)下的遠(yuǎn)程組件,是不是在老架構(gòu)的項(xiàng)目里會(huì)有什么蛛絲馬跡?

          我們開始探索老架構(gòu),這時(shí)候發(fā)現(xiàn)老架構(gòu)是使用的webpack3,而我們新架構(gòu)是使用webpack4構(gòu)建的。難道是這里出了問題???

          于是我們用webpack3重新構(gòu)建了下老架構(gòu)的遠(yuǎn)程組件,發(fā)現(xiàn)webpackJsonp對(duì)應(yīng)的確實(shí)是函數(shù),如上一節(jié)“異步加載的例子”里所示。

          所以定位到了原因,webpack4和webpack3分別構(gòu)建了新老兩種的異步遠(yuǎn)程組件,webpackJsonp在版本4下是數(shù)組,而在版本3下面是函數(shù)。

          dd75a0e2c8ff7e06c0c0637f361d6b4c.webp

          細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)上面的圖在之前出現(xiàn)過,webpack4構(gòu)建的入口文件去加載webpack3構(gòu)建的異步組件,就出現(xiàn)了章節(jié)頭出現(xiàn)的webpackJsonp不是函數(shù)的錯(cuò)誤。

          5965d6630e80bf95096735f8de9cc65d.webpimage.png

          好好想一想,大概有幾個(gè)方案。

          1. 批量去修改webpack3構(gòu)建出來的異步組件中webpackJsonp的命名,然后在容器頁面入口里自定義異步加載能力(webpackJsonp功能)的函數(shù)。
          2. 重新去用webpack4構(gòu)建所有遺留的老架構(gòu)webpack3構(gòu)建出來的異步組件。
          3. 搜尋是否有官方支持,畢竟這是一個(gè)webpack4從webpack3的過來的breack changes。

          第一個(gè)方案工作量有點(diǎn)大,且怎么保證異步組件和入口文件同步修改完畢呢?第二個(gè)方案工作量也很大,對(duì)于所有老架構(gòu)的異步組件都得更新,且更新后的可靠性堪憂,萬一有遺漏。第三個(gè)方案看起來是最靠譜的。

          1185835aad0a563c23783c39d1c873da.webp

          于是在第三個(gè)方案的方向下,開始做了搜尋。

          我們通過webpack4源碼全局搜尋webpackJsonp,發(fā)現(xiàn)了jsonpFunction。通過官方文檔找到了jsonpFunction是可以自定義webpack4的webpackJsonp的名稱。比如可以改成如下。

          output:?{
          ??//?自定義名稱
          ??jsonpFunction:?'webpack4JsonpIsArray'
          },?

          這樣后,webpackJsonp就不是一個(gè)數(shù)組了,而是未定義了。因此我們需要在我們的公共代碼庫里提供webpackJsonp函數(shù)版本的定義。如異步加載的例子小節(jié)所提到的。

          //?webpackJsonp函數(shù)版
          !(function?(n)?{
          ??window.webpackJsonp?=?function?(t,?u,?i)?{
          ??//...?
          ??}
          }([]))?

          以此來提供入口頁面能加載webpack3構(gòu)建的異步文件的能力。

          ○ 演進(jìn)

          我們還對(duì)遠(yuǎn)程組件彈層做了一些演進(jìn),由于跟本文關(guān)聯(lián)度不大,只做一些簡單介紹。

          圖片壓縮問題?

          券彈層的券有PNG、JPG、GIF格式,需要更快的展現(xiàn)速度,因此我們做了圖片壓縮的統(tǒng)一服務(wù)。

          16b1b6acaae4fa16842320773a45c43e.webpimage.png

          gif處理策略:github.com/kornelski/g…[8]?png處理策略:pngquant.org[9]?

          效率問題?

          有規(guī)律的遠(yuǎn)程組件,可通過搭建工具處理,因此我們構(gòu)建了可視化低代碼建站工具,有感興趣的同學(xué)留言,我考慮寫一篇?? 。

          4012d2012279940babb4f32adfa2d3ff.webp

          ?? 看完三件事

          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          • 點(diǎn)個(gè)【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容
          • 關(guān)注公眾號(hào)【趣談前端】,定期分享?工程化?/?可視化?/?低代碼?/?優(yōu)秀開源



          41eeaa883e3c371c4cccecad7a5dac64.webp

          從零搭建全棧可視化大屏制作平臺(tái)V6.Dooring

          從零設(shè)計(jì)可視化大屏搭建引擎

          Dooring可視化搭建平臺(tái)數(shù)據(jù)源設(shè)計(jì)剖析

          可視化搭建的一些思考和實(shí)踐

          基于Koa + React + TS從零開發(fā)全棧文檔編輯器(進(jìn)階實(shí)戰(zhàn)


          點(diǎn)個(gè)在看你最好看


          4ea657a76c5df1ad56e32942269954ec.webp
          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲天堂在线观看网站 | 九九热AV | 国产在线成人 | 精品成人人妻AV一区二区 | 高清无码成人在线观看 |