Vue中怎么自动注册组件、过滤器?

1,053 阅读3分钟

前言

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)
// ...

// 依次引入全局过滤器、指令
// 再依次进行注册
// ...

可以发现,在这种方式下,如果我们注册的内容比较多的话,这里的代码就会非常长,看起来也很冗余;并且后面要再添加新的注册内容时,每次都需要再更改这里的注册代码,非常的不优雅。

1841658051297_.pic.jpg

那有没有既简单、方便,后续添加新的注册内容时,又不用再更改注册代码的方法呢?

更优方案

当然是有的,我们可以基于 webpackrequire.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'
useSubdirectoriestrue是否查找子目录,为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$";

大家参考注释的说明,一步步往下看,就可以很清晰的看到,方法内部是怎么实现的了,是不是比我们想象的要更简单呢。

结语

我已经在项目中多次使用过啦。大家可以点赞收藏哦 👍,说不定什么时候就会用到了。

推荐阅读

👉 成为Vue高手,必须掌握的37个知识点 🔥

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿