在公司進行項目開發過程當中,咱們可能遇到相對複雜的頁面,此時咱們會把頁面拆分紅多個子組件
而後在定義一個index組件來引入全部的子組件,進而出現下面的場景:html
如今的頁面是有5個子組件,那麼須要引入5次,而後註冊5次,最後還要在html中渲染5次,
有了這個理論,那麼若是一個頁面須要8個子組件,10個子組件呢,
那麼咱們的index文件的代碼將會變得冗餘,並且變得很差維護,每增長一個子組件文件的話,
咱們都須要在index文件中去維護一次。前端
瞭解了多個子組件引入到index有這些痛點,接下來咱們就去優化下。vue
若是咱們把上面三步所有實現,是否是就能夠解決上面說的幾個痛點呢?webpack
這裏推出一個新的api,require.context,什麼是require.context呢?git
概念github
一個webpack的api,經過執行require.context函數獲取一個特定的上下文,
主要用來實現自動化導入模塊,在前端工程中,若是遇到從一個文件夾引入不少模塊的狀況,
可使用這個api,它會遍歷文件夾中的指定文件,而後自動導入,使得不須要每次顯式的調用import導入模塊。web
語法正則表達式
require.context( directory, (useSubdirectories = true), (regExp = /^\.\/.*$/), );
require.context接收三個參數: directory:一個要搜索的目錄 useSubdirectories:一個標記表示是否還搜索其子目錄 regExp:一個匹配文件的正則表達式。
使用json
child-components文件夾下有四個vue組件:car.vue,custom.vue,news.vue,sports.vue.segmentfault
let files = require.context('./child-components', false, /\.vue$/) let modules = {} files.keys().forEach(key => { const moduleName = key.replace(/(\.\/|\.vue)/g, '') modules[moduleName] = files(key).default }) export default modules
代碼分析:
require.context函數執行後返回的是一個函數,而且這個函數有3個屬性id,keys,resolve,
這裏咱們重點分析下keys,keys是一個函數function webpackContextKeys,
keys()執行後返回的是正則匹配成功的模塊名字(正則匹配的文件名的相對路徑)的數組
上面說到require.context函數執行後返回的是一個函數,它接收一個參數req,
這個參數就是正則匹配的文件名的相對路徑,就是keys()函數返回的數組中的每一項,
此時require.context函數的結果賦值給變量files,而後去執行
files(參數:正則匹配的文件名的相對路徑)函數執行後獲得一個Module對象,
Module對象上有一個default屬性,值就是咱們須要引入的組件
這個Module對象上的default屬性
和咱們經過import Car from './child-components/car.vue'引入的Car是同樣的
分析完了require.context函數和require.context返回的三個屬性,咱們繼續分析後面的代碼:
如今咱們已經能夠獲取到某一個文件夾下全部的vue組件了,咱們但願導出一個對象,
將每個vue組件根據文件名按照映射關係存儲起來而後導出,這裏能夠提早看下咱們導出的對象的結構:
固然上圖中標記的對象key也能夠自定義,咱們這裏採用的是vue文件的名字,由於這樣能夠直觀的看出
它們之間的映射關係,car屬性對應的就是car.vue組件。
咱們先定義一個空對象,做爲咱們最終導出的對象
let modules = {}
由於keys()執行後返回的是正則匹配成功的模塊名字(正則匹配的文件名的相對路徑)的數組,
因此咱們對數組進行遍歷,首先獲取到每個vue文件的名字,由於咱們如今手裏有的是vue文件的相對路徑(./car.vue),因此咱們須要對./car.vue進行正則匹配處理獲取到car,代碼以下:
key.replace(/(\.\/|\.vue)/g, '')
此時咱們須要導出的對象已經有了key值,如今就差value值了,即咱們須要引入的vue組件模塊
上面分析require.context函數的過程當中已經提到,require.context函數的仍然是一個函數,
這個函數接收一個參數req,這個req就是正則匹配成功的模塊名字,
files(key).default
那麼files(key).default返回的就是咱們須要引入的vue組件模塊,
那麼modules對象的key和value值都已經有了 !!!
至此咱們已經經過require.context就能夠獲取到child-components文件夾下面的全部vue文件了
而後將modules引入到父組件index.vue中
這樣咱們就完成了第一步優化!
頁面實現效果:
先介紹下咱們的需求和項目結構文件:
因爲咱們作的頁面屬於模塊配置化的,因此說頁面上顯示哪些組件是根據後端返回的數據來顯示的。
咱們想要在頁面顯示這四個組件(按照這個順序來):新聞組件、自定義組件、汽車組件、體育組件。
每一個組件裏面都有一個input輸入框,當咱們點擊提交時,要將每一個組件綁定到input輸入框的value收集起來。
第一步:咱們要在data中定義後端返回數據(這裏咱們就本地自定義數組來模擬):
setFileShow:[ //該數組爲後端返回數據,告知前端須要展現那些模塊 //action 是必須的,action的值 須要與fileModules數組中對象裏面的action值作映射 //data屬性對象中的content和title爲組件的標題和介紹(這兩個屬性值也是由後端來定義的) //此數組返回來的數據直接決定了最終頁面展現模塊的順序 { action:"News", data:{ content:'我是新聞組件', title:"新聞" } }, { action:"Car", data:{ content:'我是汽車組件', title:"汽車" } }, { action:"Sports", data:{ content:'我是體育組件', title:"體育" } }, ]
第二步:咱們去拿到咱們已經獲取到的咱們本地的四個組件
import modules from './index.js'
這裏的modules數據結構是這樣的
{ car:car組件, custom:custom組件, news:news組件, sports:sports組件 }
如今咱們來分析下前面兩步,咱們沒法經過後端返回的數據來動態去渲染咱們的組件,
也就是說咱們沒法將setFileShow和modules之間創建映射關係,
(由於後端返回的action值首字母爲大寫,而咱們的modules的key值都是小寫的)
(若是後端返回的action值與咱們的modules的key值是同樣的,
那咱們能夠忽略第三步,直接看第四步的代碼B)
第三步:咱們要在data中創建一個數組fileModules,將setFileShow和modules聯繫起來
fileModules:[ //這裏定義後端返回數據與全部子組件文件的映射關係 沒有順序要求 // 以Car:'car', 爲例,action中Car(大寫)爲後端返回數據的主鍵 //filename中car(小寫)爲子組件的文件名(即modules的key值) { action:"Car", filename:'car' }, { action:"News", filename:'news', }, { action:"Sports", filename:'sports', } ],
第四步:就是生成咱們要去動態渲染組件的數組了
代碼A和代碼B只會執行一種,請你們根據實際狀況來選擇
代碼A: computed:{ componentList(){ let arr = [] //定義最終展現那些模塊的數組 let setFileShow = this.setFileShow //後端返回數據 let fileModules = this.fileModules //映射關係 for(let i=0;i<setFileShow.length;i++){ for(let j=0;j<fileModules.length;j++) { if(setFileShow[i].action == fileModules[j].action) { arr.push({ file:modules[fileModules[j].filename],//modules中的value值(vue組件) data:setFileShow[i].data, //組件中須要的標題和介紹 name:fileModules[j].filename // name爲 modules中的key值(vue組件名稱)這裏收集name是爲了給每一個組件綁定ref值 //當提交數據時,能夠經過this.$refs去獲取到全部的組件對象 }) } else { } } } //由於咱們的項目須要自定義配置,咱們的需求是在新聞組件和汽車組件之間插入, //那麼根據此時arr中新聞組件和汽車組件的索引值, arr.splice(1,0,{file: modules['custom'], data: {/*這裏是自定義數據*/},name:"custom"}) //這樣的話,咱們最終渲染頁面的組件就是 新聞、自定義、汽車、體育這樣的順序顯示了 return arr },
代碼B: //(這種只針對若是後端返回的action值與咱們的modules的key值是同樣的, 若是action值不同,須要像代碼A那樣去整合) computed:{ componentList(){ let arr = [] let setFileShow = this.setFileShow //後端返回數據 for(let i=0;i<this.setFileShow.length;i++){ for(let j in modules) { if(setFileShow[i].action == j){ arr.push({ file:modules[j], //modules中的value值(vue組件) data:setFileShow[i].data,//組件中須要的標題和介紹 name:j, // name爲 modules中的key值(vue組件)這裏收集name是爲了給每一個組件綁定ref值 //當提交數據時,能夠經過this.$refs去獲取到全部的組件對象 }) } } } return arr } }
第五步:咱們須要來處理一些特殊場景了(若是須要的話)
細心的同窗應該發現了, 咱們模擬的後端返回數據setFileShow中只有三個對象, 分別對應news(新聞組件)、car(汽車組件)、sports(體育) 三個組件,可是咱們的頁面卻顯示了4個組件,這個就是咱們要講的自定義配置了。 在實際開發過程當中,後端只返回了3個須要進行配置化的數據, 可是咱們的頁面須要4個、5個或者更多的組件,另外這些組件並不在配置化的範圍, 並且咱們在點擊提交按鈕,仍然會收集這些組件的信息, 這時就須要咱們本身手動去把這些組件加入到動態組件的數據中。
//若是須要在全部組件最前面插入模塊 //arr.unshift( { file: modules['custom'], //咱們手動去獲取下custom組件 data: {/*這裏是自定義數據*/}, name: 'custom' }) //若是須要在全部組件最前面後插入模塊 //arr.push( { file: modules['custom'], //咱們手動去獲取下custom組件 data: {/*這裏是自定義數據*/}, name: 'custom' }) //若是須要插入到全部組件中的某一個位置 //arr.splice(1,0, { file: modules['custom'], //咱們手動去獲取下custom組件 data: {/*這裏是自定義數據*/}, name:"custom" })
最後將上述代碼插入到computed中的componentList方法中去, 記得在return arr以前循環以後就能夠了 接下來就是要把咱們經過computed中的componentList方法返回的arr, 就是咱們最終須要動態渲染組件的數據了。 咱們來看下咱們最終整合出來的數組結構
下面咱們一段僞代碼來描述上面圖片中瀏覽器打印出來的信息
[ { data:{ content:'我是新聞組件', title:"新聞" }, file:新聞組件 name:"news" }, { data:{ }, file:自定義組件 name:"custom" }, { data:{ content:'我是汽車組件', title:"汽車" }, file:汽車組件, name:"car" }, { data:{ content:'我是體育組件', title:"體育" }, file:體育組件, name:"sports" } ]
如今咱們已經拿到咱們須要的數據了,那麼咱們開始去動態渲染組件吧
<component class="mb" v-for="(item,i) in componentList" :ref="item.name" :key="i" :is="item.file" :data="item.data" ></component>
ref是爲每一個組件綁定一個值,方便下面提交時收集各個組件的數據
key爲循環key值
is是Vue動態建立組件方法的屬性,須要組件做爲參數
data是咱們須要分發給每一個子組件的數據
下面以car組件爲例,看下car組件內部是如何渲染的
car組件中的data.title和data.content都是經過動態組件分發到子組件的數據
data函數中carData是自定義數據,value值是input輸入框的默認值,
最終點擊提交按鈕時,咱們能夠將carData中的數據 整合成後端須要的json數據傳遞過去
最後一步:就是整合每一個子組件裏面的數據提交到後端了
let obj = {} //定義向後端傳輸數據 let componentFiles = this.$refs //全部子組件的集合 此處是一個對象 //咱們對這個對象進行遍歷 for(let item in componentFiles) { //這裏的item就是咱們咱們綁定的每個ref的值 //這裏的componentFiles[item]的值爲1個數組,數組中只有一個組件VueComponent對象 //這裏的fileData就是每個組件的VueComponent對象上的_data屬性值 //fileData的值其實就是每一個子組件中data函數中return返回的對象 //以car組件爲例就是 // { // checked:false, // carData:{ //提交後端數據 // value:"汽車" // } // } let fileData = componentFiles[item][0]._data //根據不一樣的ref綁定值,咱們去對應的子組件去獲取對應的數據,而後都綁定到obj對象上 //最終將obj裏面的數據傳遞給後端 switch (item) { case 'car': obj.car = fileData.carData //此處obj後面的car爲測試使用 實際以提交數據真實的key爲準 break case 'news': obj.news = fileData.newsData break case 'sports': obj.sports = fileData.sportsData break } }
讓咱們來看下咱們最終提交的數據格式:
這樣的話咱們完成了第二步的優化!
本文的實戰項目地址:
https://github.com/dabaoRain/...
做者對於require.context和Vue動態組件的理解屬於基礎入門級別,對於文章中的理解或者使用錯誤,望各位大神不吝指出,關於require.context和Vue動態組件有那些須要補充的也能夠進行評論,做者不勝感激。排版碼字不易,以爲對您有所幫助,就幫忙點個贊吧!