【技术创作101训练营】三种不同场景下 vue 组件动态加载的方法及实现
Write By CS逍遥剑仙 我的主页: www.csxiaoyao.com GitHub: github.com/csxiaoyaojianxian Email: sunjianfeng@csxiaoyao.com
1. 背景
前端模块化开发模式已成主流,但随着前端项目规模的不断扩大,开发者可能会遇到以下一些问题:
- 不仅打包的效率越来越低下,而且打包后的文件体积也不断增加;
- 首屏加载文件过大,白屏时间过长;
- 有时,加载的组件名称不确定,需要动态确定需要加载的组件;
- 整体打包导致大型项目若需要扩展组件,开发者必须下载完整工程,被迫开放源码,且易冲突
本文将选用 vue 框架,使用三种方式实现前端模块的动态加载,分别解决上述的一个或多个问题。
2. vue 动态 & 异步组件
在大型应用中,我们常常需要将应用切分,在客户端请求时按需加载,减少首次请求的文件体积,并缓存供下次使用。vue 框架提供了动态组件 & 异步组件功能 ( 文档地址 )。
2.1 动态组件实现组件动态切换
动态组件即通过 is
属性来动态地切换不同的组件:
<component v-bind:is="currentComponent"></component>
2.2 异步组件实现懒加载
而异步组件则允许以工厂函数的方式定义组件并异步解析,只有当该组件需要被渲染时才会触发该工厂函数,并缓存组件供下次使用:
Vue.component(
'async-example',
() => import('./async-component') // 返回一个 Promise 对象
)
异步组件同样适用于组件的局部注册方式:
new Vue({
components: {
'async-component': () => import('./async-component')
}
})
2.3 动态 & 异步组件实现组件动态加载
结合动态组件和异步组件的特性,即可轻松实现动态加载,即修改动态组件的 is
标签,触发异步组件的加载。
3. webpack - require.context
使用 webpack 打包,模块需要通过 es6-import
或是 require
的方式导入。当导入的组件较多时,一个个导入,会比较繁琐。【方式2】使用 vue 的动态&异步组件实现了懒加载,但需要显式地指定所有需要加载的组件,幸运的是,webpack 提供了 require.context
的 api 供开发者动态导入模块,这样开发者甚至可以根据接口返回动态地加载组件,下面是一个 demo:
<template>
<div id="app">
<component :is="app"></component>
</div>
</template>
<script>
import Vue from 'vue'
export default {
data () {
return {
app: ''
}
},
created () {
// 模拟异步请求
setTimeout(() => {
const requireComponent = require.context('./comps', false, /comp[0-9]+.(vue|js)$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = fileName.split('/').pop().replace(/.w+$/, '')
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
// 修改动态组件 is 标签
setTimeout(() => {
this.app = 'comp2'
}, 2000)
}, 1000)
}
}
</script>
require.context
需要传入三个参数,参数1为组件目录的相对路径,参数2为是否查询其子目录,参数3为匹配组件文件名的正则表达式:
const requireComponent = require.context(
'./comps', // 其组件目录的相对路径
false, // 是否查询其子目录
/comp[0-9]+.(vue|js)$/ // 匹配组件文件名的正则表达式
)
遍历 require.context
返回值的 key,并注册,若这个组件选项是通过 export default
导出的会优先使用 .default
。
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName) // 获取组件配置
const componentName = fileName.split('/').pop().replace(/.w+$/, '')
// 全局注册组件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
4. vue 子组件独立打包
上面的【方式3】解决了【方式2】显式指定全部组件的不便,但动态组件仍需要和主项目一起打包,在一些场景下则显得不便,最理想的状态应该是:主程序和子组件独立打包,能够根据异步接口的返回结果动态地加载组件。
4.1 webpack + vue-loader
webpack 中 vue 子组件独立打包,需要使用对应的 vue-loader
识别 vue 文件,见 01-webpack
,可以参考 vue-loader
文档,vue-loader文档地址,需要配置 webpack.config.js
{
test: /.vue$/,
loader: 'vue-loader'
}
构建的组件的使用
<!-- 动态组件 -->
<div :is="compName"></div>
动态载入 build 后的 bundle
loadScript('xxx').then(() => {
Vue.component(compName, window[compName].default);
this.compName = compName;
})
4.2 vue-cli
vue 官方提供的脚手架 vue-cli
集成了 vue-loader
,开箱即用,可以轻松实现独立入口打包。见 02-vue-lib
,参考 vue-cli
文档,vue-cli 文档地址:
# 将一个单独的入口构建为一个库
$ vue-cli-service build --target lib --name myLib [entry]
4.3 导入动态组件脚本文件
经过打包的组件可以生成 js 脚本文件或 lib 库文件,可以根据接口等方式的返回结果,通过 script
或 import
的方式引入,见 03-vue-lib
loadScript('http://xxx/xx.js').then(() => {
Vue.component(compName, window[compName].default || window[compName]);
this.compName = compName;
})
5. 使用场景总结
本文总结了三种组件动态加载的方式,其中:
(1) vue 动态 & 异步组件的方式最简单,能够实现组件的懒加载,可以在普通项目中直接使用,但需要显式地指定所有动态组件并和主程序一起打包,适合大部分场景;
(2) webpack 的 require.context 方式支持用正则表达式的方式异步导入组件,适合导入多个文件名满足一定规律的组件,并且支持从接口等方式,根据返回结果异步加载组件,但是仍然需要和主程序一起打包,适合多人同时在一个项目下开发,并且子组件迭代频繁,需要通过文件名的正则表达式动态载入的场景;
(3) 子组件独立打包的方式通过 vue-loader
等 webpack 插件,对子组件独立打包,并根据接口返回结果动态加载,但是实现较为复杂,适合大型系统用于扩展插件,或者在不开放源码的前提下实现项目间的组件调用。
独立打包不仅能够缩短项目的打包时间,减少打包文件体积,加快加载速度,还能实现项目间的组件调用。在实践中,我们需要根据不同场景选择适合的方式。
6. 参考资料
- 动态组件 & 异步组件 (https://cn.vuejs.org/v2/guide/components-dynamic-async.html)
- Webpack-require.context (https://webpack.js.org/guides/dependency-management/#requirecontext)
- Vue-loader手动设置 (https://vue-loader.vuejs.org/zh/guide/#手动设置)
- Vue-cli 应用 (https://cli.vuejs.org/zh/guide/build-targets.html#应用)
- 测试网站页面网速的一个简单Python脚本
- 框架页面尽可以这么用(后置代码中控制框架)
- 微信小程序「学科排名」发布了
- Nginx 负载均衡的Cache缓存批量清理的操作记录
- DotNet软件开发框架
- Nginx通过https方式反向代理的简单实现
- 再论IBatisNet + Castle进行项目的开发
- 利用xml轻松读取web.config中的用户自定义节
- 分布式监控系统Zabbix-3.0.3-完整安装记录(3)-监控nginx,php,memcache,Low-level discovery磁盘IO
- python报错问题解决:'ascii' codec can't encode character
- 利用message queue实现aspx与winform通信, 并附完整示例
- 10招步骤保护IIS服务器安全
- Haproxy+Keepalived高可用环境部署梳理(主主和主从模式)
- Android-Universal-Image-Loader 图片异步加载类库的使用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法