乐趣区

关于前端:Vue动态组件和requirecontext实现单页面多组件配置化引入

场景

在公司进行我的项目开发过程中,咱们可能遇到绝对简单的页面,此时咱们会把页面拆分成多个子组件
而后在定义一个 index 组件来引入所有的子组件,进而呈现上面的场景:

当初的页面是有 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,

这里咱们重点剖析下 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 动静组件有那些须要补充的也能够进行评论,作者不胜感激。排版码字不易,感觉对您有所帮忙,就帮忙点个赞吧!

退出移动版