Vue動態組件和require.context實現單頁面多組件配置化引入

場景

在公司進行項目開發過程當中,咱們可能遇到相對複雜的頁面,此時咱們會把頁面拆分紅多個子組件
而後在定義一個index組件來引入全部的子組件,進而出現下面的場景:html

11111111.png

如今的頁面是有5個子組件,那麼須要引入5次,而後註冊5次,最後還要在html中渲染5次,
有了這個理論,那麼若是一個頁面須要8個子組件,10個子組件呢,
那麼咱們的index文件的代碼將會變得冗餘,並且變得很差維護,每增長一個子組件文件的話,
咱們都須要在index文件中去維護一次。前端

瞭解了多個子組件引入到index有這些痛點,接下來咱們就去優化下。vue

優化思路

  1. 一次引入所有子組件(只執行一次import子組件)
  2. 利用循環去循環註冊引入的子組件,而不用每一個都去註冊一遍
  3. 在html中只去渲染一次就能夠動態渲染全部的子組件呢 (動態組件渲染)

若是咱們把上面三步所有實現,是否是就能夠解決上面說的幾個痛點呢?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,

11111111.png

這裏咱們重點分析下keys,keys是一個函數function webpackContextKeys,

keys()執行後返回的是正則匹配成功的模塊名字(正則匹配的文件名的相對路徑)的數組

11111111.png

上面說到require.context函數執行後返回的是一個函數,它接收一個參數req,

這個參數就是正則匹配的文件名的相對路徑,就是keys()函數返回的數組中的每一項,

此時require.context函數的結果賦值給變量files,而後去執行

files(參數:正則匹配的文件名的相對路徑)函數執行後獲得一個Module對象,

Module對象上有一個default屬性,值就是咱們須要引入的組件

11111111.png

這個Module對象上的default屬性
和咱們經過import Car from './child-components/car.vue'引入的Car是同樣的

11111111.png

分析完了require.context函數和require.context返回的三個屬性,咱們繼續分析後面的代碼:

如今咱們已經能夠獲取到某一個文件夾下全部的vue組件了,咱們但願導出一個對象,
將每個vue組件根據文件名按照映射關係存儲起來而後導出,這裏能夠提早看下咱們導出的對象的結構:

11111111.png

固然上圖中標記的對象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中

這樣咱們就完成了第一步優化!

優化第二步

頁面實現效果:

11111111.png

項目結構

11111111.png

先介紹下咱們的需求和項目結構文件:

因爲咱們作的頁面屬於模塊配置化的,因此說頁面上顯示哪些組件是根據後端返回的數據來顯示的。

咱們想要在頁面顯示這四個組件(按照這個順序來):新聞組件、自定義組件、汽車組件、體育組件。

每一個組件裏面都有一個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,

就是咱們最終須要動態渲染組件的數據了。

咱們來看下咱們最終整合出來的數組結構

11111111.png

下面咱們一段僞代碼來描述上面圖片中瀏覽器打印出來的信息

[
      {
         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組件內部是如何渲染的

11111111.png

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     
    }
  }

讓咱們來看下咱們最終提交的數據格式:

11111111.png

11111111.png

這樣的話咱們完成了第二步的優化!

本文的實戰項目地址:
https://github.com/dabaoRain/...

做者對於require.context和Vue動態組件的理解屬於基礎入門級別,對於文章中的理解或者使用錯誤,望各位大神不吝指出,關於require.context和Vue動態組件有那些須要補充的也能夠進行評論,做者不勝感激。排版碼字不易,以爲對您有所幫助,就幫忙點個贊吧!

相關文章
相關標籤/搜索