Vue动态组件和require.context实现单页面多组件配置化引入

场景 在公司进行项目开发过程中,我们可能遇到相对复杂的页面,此时我们会把页面拆分成多个子组件
然后在定义一个index组件来引入所有的子组件,进而出现下面的场景:
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

现在的页面是有5个子组件,那么需要引入5次,然后注册5次,最后还要在html中渲染5次,
有了这个理论,那么如果一个页面需要8个子组件,10个子组件呢,
那么我们的index文件的代码将会变得冗余,而且变得不好维护,每增加一个子组件文件的话,
我们都需要在index文件中去维护一次。
了解了多个子组件引入到index有这些痛点,接下来我们就去优化下。
优化思路

  1. 一次引入全部子组件(只执行一次import子组件)
  2. 利用循环去循环注册引入的子组件,而不用每个都去注册一遍
  3. 在html中只去渲染一次就可以动态渲染所有的子组件呢 (动态组件渲染)
如果我们把上面三步全部实现,是不是就可以解决上面说的几个痛点呢?
优化第一步 (一次引入全部子组件) 这里推出一个新的api,require.context,什么是require.context呢?
概念
一个webpack的api,通过执行require.context函数获取一个特定的上下文,
主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况,
可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块。
语法
require.context( directory, (useSubdirectories = true), (regExp = /^\.\/.*$/), );

require.context接收三个参数: directory:一个要搜索的目录 useSubdirectories:一个标记表示是否还搜索其子目录 regExp:一个匹配文件的正则表达式。

使用
child-components文件夹下有四个vue组件:car.vue,custom.vue,news.vue,sports.vue.
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,
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

这里我们重点分析下keys,keys是一个函数function webpackContextKeys,
keys()执行后返回的是正则匹配成功的模块名字(正则匹配的文件名的相对路径)的数组
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

上面说到require.context函数执行后返回的是一个函数,它接收一个参数req,
这个参数就是正则匹配的文件名的相对路径,就是keys()函数返回的数组中的每一项,
此时require.context函数的结果赋值给变量files,然后去执行
files(参数:正则匹配的文件名的相对路径)函数执行后得到一个Module对象,
Module对象上有一个default属性,值就是我们需要引入的组件
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

这个Module对象上的default属性
和我们通过import Car from './child-components/car.vue'引入的Car是一样的
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

分析完了require.context函数和require.context返回的三个属性,我们继续分析后面的代码:
现在我们已经可以获取到某一个文件夹下所有的vue组件了,我们希望导出一个对象,
将每一个vue组件根据文件名按照映射关系存储起来然后导出,这里可以提前看下我们导出的对象的结构:
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

当然上图中标记的对象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中
这样我们就完成了第一步优化!
优化第二步 页面实现效果:
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

项目结构 Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

先介绍下我们的需求和项目结构文件:
由于我们做的页面属于模块配置化的,所以说页面上显示哪些组件是根据后端返回的数据来显示的。
我们想要在页面显示这四个组件(按照这个顺序来):新闻组件、自定义组件、汽车组件、体育组件。
每个组件里面都有一个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)
【Vue动态组件和require.context实现单页面多组件配置化引入】第三步:我们要在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

代码B: //(这种只针对如果后端返回的action值与我们的modules的key值是一样的, 如果action值不一样,需要像代码A那样去整合) computed:{ componentList(){ let arr = [] let setFileShow = this.setFileShow //后端返回数据 for(let i=0; i

第五步:我们需要来处理一些特殊场景了(如果需要的话)
细心的同学应该发现了, 我们模拟的后端返回数据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,就是我们最终需要动态渲染组件的数据了。我们来看下我们最终整合出来的数组结构

Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

下面我们一段伪代码来描述上面图片中浏览器打印出来的信息
[ { data:{ content:'我是新闻组件', title:"新闻" }, file:新闻组件 name:"news" }, { data:{}, file:自定义组件 name:"custom" }, { data:{ content:'我是汽车组件', title:"汽车" }, file:汽车组件, name:"car" }, { data:{ content:'我是体育组件', title:"体育" }, file:体育组件, name:"sports" } ]

现在我们已经拿到我们需要的数据了,那么我们开始去动态渲染组件吧

ref是为每个组件绑定一个值,方便下面提交时收集各个组件的数据
key为循环key值
is是Vue动态创建组件方法的属性,需要组件作为参数
data是我们需要分发给每个子组件的数据
下面以car组件为例,看下car组件内部是如何渲染的
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

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 = https://www.it610.com/article/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 } }

让我们来看下我们最终提交的数据格式:
Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

Vue动态组件和require.context实现单页面多组件配置化引入
文章图片

这样的话我们完成了第二步的优化!
本文的实战项目地址:
https://github.com/dabaoRain/...
作者对于require.context和Vue动态组件的理解属于基础入门级别,对于文章中的理解或者使用错误,望各位大神不吝指出,关于require.context和Vue动态组件有那些需要补充的也可以进行评论,作者不胜感激。排版码字不易,觉得对您有所帮助,就帮忙点个赞吧!

    推荐阅读