前言
在Vue
开发中,我们是不是经常会遇到下面这种,注册全局组件、过滤器、指令的场景:
// 依次引入全局组件
import baseButton from '@/globalComponents/baseButton.vue'
import baseDialog from '@/globalComponents/baseDialog.vue'
import baseSlider from '@/globalComponents/baseSlider.vue'
import baseRadio from '@/globalComponents/baseRadio.vue'
// ...
// 再依次进行注册
Vue.component('baseButton', baseButton)
Vue.component('baseDialog', baseDialog)
Vue.component('baseSlider', baseSlider)
Vue.component('baseRadio', baseRadio)
// ...
// 依次引入全局过滤器、指令
// 再依次进行注册
// ...
可以发现,在这种方式下,如果我们注册的内容比较多的话,这里的代码就会非常长,看起来也很冗余;并且后面要再添加新的注册内容时,每次都需要再更改这里的注册代码,非常的不优雅。
那有没有既简单、方便,后续添加新的注册内容时,又不用再更改注册代码的方法呢?
更优方案
当然是有的,我们可以基于 webpack
的 require.context
来实现自动引入内容并注册。
我们先来看下,使用这种方式后注册组件的代码。
// 获取指定文件下组件
const requireComponent = require.context(
'./globalComponents', false, /\.vue$/
)
// 注册组件
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
Vue.component(componentName, componentConfig.default)
})
怎么样,看起来是不是简洁很多,并且让我们的代码都变的更高级一点了。后面要是再在我们指定的目录中添加组件,也会自动注册啦!🎉
如果上面的代码有不明白的地方,不要紧,下面我就来给大家介绍下
require.context
是怎么实现这个功能的。💪
require.context 可以用来做什么
简单一句话理解:找到指定目录下,你想要的文件集合
基于此特性,我们就可以实现一些有意思的功能。如:根据指定的目录,批量注册组件、过滤器、指令等。
基本语法
require.context(
directory
(useSubdirectories = true),
(regExp = /^\.\/.*$/),
(mode = 'sync')
);
参数说明
参数 | 是否必填 | 默认值 | 说明 |
---|---|---|---|
directory | 是 | 要查找的文件目录,如 './components' | |
useSubdirectories | 否 | true | 是否查找子目录,为false 时则只在directory 根目录下进行查找 |
regExp | 否 | /^\.\/.*$/ | 查找文件的正则表达式,如查找.vue 类型的文件: /\.vue$/ |
mode | 否 | 'sync' | 查找方式,同步还是异步。默认同步就行,很少使用这个参数 |
返回值
context 对象
一个根据模块request
,来获取模块内容
的函数
context 对象 :3个属性
- keys:函数类型,执行后返回所有匹配模块的
模块request
数组。模块request:匹配的文件 对于 查找目录 的相对路径
- resolve:函数类型,根据
模块request
返回模块id
,较少使用。模块id:匹配的文件 对于 项目根目录 的相对路径
- id: 文件查找规则,很少使用,可以忽略。
我们要实现的功能只需要使用 context
函数 和 keys
函数,可以先只关注这2个。
属性实例说明
我们来通过一个简单的示例,看看这些属性最终的返回值都是什么?来帮助我们更好的理解。
// 查找 components 文件夹下,所有的 .vue 文件。返回 context 对象
const requireComponent = require.context('./components', true, /\.vue$/)
// 调用 keys 函数,返回所有匹配模块的 模块request 列表
requireComponent.keys()
// ['./importStep.vue', './phoneModel.vue']
// 调用 resolve 函数,传入 模块request,返回 模块id
requireComponent.resolve('./phoneModel.vue')
// './src/components/phoneModel.vue'
// context 的规则
requireComponent.id
// './src/components sync \.vue$'
// 执行 context 函数,传入 模块request,返回 模块内容[就是我们模块内部 export导出 的内容]
requireComponent('./phoneModel.vue')
// 返回值为.vue文件中 export 导出的内容
通过 context
函数 ,和我们 import
引入组件 拿到的内容是一样的,如下:
import phoneModel from '@/components/phoneModel.vue'
// 最终他们指向的值是相同的,都为我们组件的实例对象
requireComponent('./phoneModel.vue').default === phoneModel // true
好,目前为止,我们对这些属性的使用应该基本都清楚了。接下来我们想想,应该怎么去写好一个自动注册全局组件的方法?
实例
简单的文字说明,总是显得朴实无华且枯燥。下面我们来通过实例来演示下,让大家更好理解一点
全局组件的注册
先约定好,我们所有的全局组件统一放在components
文件夹下,components
文件结构如下
+ components
- importStep.vue
- phoneModel.vue
接下来获取components
文件夹下所有的.vue
文件,并进行注册。
// 1、初始化,找到 components 文件跟目录下以 .vue 命名的文件
const requireComponent = require.context(
'./components', false, /\.vue$/
)
// 2、获取 模块request 列表,并进行遍历
requireComponent.keys().forEach(fileName => {
// 3、通过 context函数 获取对应文件导出的内容
const componentConfig = requireComponent(fileName)
// 4、格式化组件名
// 因为得到的filename格式是: './baseButton.vue', 所以这里我们去掉头和尾,只保留真正的文件名(baseButton)
const componentName = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
// 5、全局组件的注册
Vue.component(componentName, componentConfig.default)
})
怎么样,了解了这些属性的含义,再来实现自动注册的功能是不是就很简单了 😊。
那如果是要注册全局的过滤器或者指令呢?应该怎么写,这里大家可以尝试下,基本原理和组件的注册是一样的:拿到文件内容 -> 进行注册(或做其他的事情)。
看完上面的内容,我们就可以在项目中比较好的使用这个方法了。
执行机制详解
深入理解它的执行机制后,我们可以更灵活的将它应用到更多的场景。
比如我在一个可视化搭建的项目中,有非常多的可选组件配置进行组合,最终组成一个页面。在这个页面渲染的时候,就会涉及到动态组件的循环渲染,这时 使用这个方法,就是非常合适的,拿到我们所有可配置的组件,并在我们搭建的渲染页面中自动批量注册。而不仅仅是只能在全局中进行注册。
下面主要对 require.context
内部的实现,做个简单的介绍,可以帮忙大家更好的理解。如果感兴趣可以接着往下看哦。
// 获取context对象
const requireComponent = require.context('./components', true, /\.vue$/)
我们可以看看执行上面的代码时,require.context
函数 内部是怎么处理的?
// 1、根据文件查找方法找到我们components目录下所有匹配到的文件路径。
// 2、根据找到的文件路径,生成map对象
// key:模块request(文件相对于components目录的相对路径)
// value:模块id(文件相对于项目根目录的相对路径)
var map = {
"./importStep.vue": "./src/components/importStep.vue",
"./phoneModel.vue": "./src/components/phoneModel.vue"
};
// require.context() 的返回值,下面我们统称为context
// 入参req为 模块request
function webpackContext(req) {
var id = webpackContextResolve(req); // 根据模块request返回模块id
// 根据模块id,返回引入的文件。其实就是执行require()。
// 如: require('../src/components/phoneModel.vue')
return __webpack_require__(id);
}
// 根据模块request,找到对应模块id的函数,可以简单理解为获取map中指定key的value
function webpackContextResolve(req) {
if(!__webpack_require__.o(map, req)) {
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}
return map[req];
}
// context提供的keys函数:遍历map对象的key,返回key组成的数组(模块request数组)
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
// context 提供的 resolve函数
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext; // 抛出context,也就是函数的返回值
// context的id,目录、规则等组成的字符串
webpackContext.id = "./src/components sync \\.vue$";
大家参考注释的说明,一步步往下看,就可以很清晰的看到,方法内部是怎么实现的了,是不是比我们想象的要更简单呢。
结语
我已经在项目中多次使用过啦。大家可以点赞收藏哦 👍,说不定什么时候就会用到了。
推荐阅读
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。