三种不同场景下 vue 组件动态加载的方法及实现
Write By CS逍遥剑仙
我的主页: csxiaoyao.com
GitHub: github.com/csxiaoyaojianxian
Email: sunjianfeng@csxiaoyao.com
QQ: 1724338257
1. 背景
前端模块化开发模式已成主流,但随着前端项目规模的不断扩大,开发者可能会遇到以下一些问题:
- 不仅打包的效率越来越低下,而且打包后的文件体积也不断增加;
- 首屏加载文件过大,白屏时间过长;
- 有时,加载的组件名称不确定,需要动态确定需要加载的组件;
- 整体打包导致大型项目若需要扩展组件,开发者必须下载完整工程,被迫开放源码,且易冲突
本文将选用 vue 框架,使用三种方式实现前端模块的动态加载,分别解决上述的一个或多个问题。
2. vue 动态 & 异步组件
在大型应用中,我们常常需要将应用切分,在客户端请求时按需加载,减少首次请求的文件体积,并缓存供下次使用。vue 框架提供了动态组件 & 异步组件功能 ( 文档地址 )。
2.1 动态组件实现组件动态切换
动态组件即通过 is
属性来动态地切换不同的组件:
<code class="language-vue"><component v-bind:is="currentComponent"></component></code>
2.2 异步组件实现懒加载
而异步组件则允许以工厂函数的方式定义组件并异步解析,只有当该组件需要被渲染时才会触发该工厂函数,并缓存组件供下次使用:
<code class="language-vue">Vue.component( 'async-example', () => import('./async-component') // 返回一个 Promise 对象 )</code>
异步组件同样适用于组件的局部注册方式:
<code class="language-vue">new Vue({ components: { 'async-component': () => import('./async-component') } })</code>
2.3 动态 & 异步组件实现组件动态加载
结合动态组件和异步组件的特性,即可轻松实现动态加载,即修改动态组件的 is
标签,触发异步组件的加载。
3. webpack – require.context
使用 webpack 打包,模块需要通过 es6-import
或是 require
的方式导入。当导入的组件较多时,一个个导入,会比较繁琐。【方式2】使用 vue 的动态&异步组件实现了懒加载,但需要显式地指定所有需要加载的组件,幸运的是,webpack 提供了 require.context
的 api 供开发者动态导入模块,这样开发者甚至可以根据接口返回动态地加载组件,下面是一个 demo:
<code class="language-vue"><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></code>
require.context
需要传入三个参数,参数1为组件目录的相对路径,参数2为是否查询其子目录,参数3为匹配组件文件名的正则表达式:
<code class="language-vue">const requireComponent = require.context( './comps', // 其组件目录的相对路径 false, // 是否查询其子目录 /comp[0-9]+\.(vue|js)$/ // 匹配组件文件名的正则表达式 )</code>
遍历 require.context
返回值的 key,并注册,若这个组件选项是通过 export default
导出的会优先使用 .default
。
<code>requireComponent.keys().forEach(fileName => { const componentConfig = requireComponent(fileName) // 获取组件配置 const componentName = fileName.split('/').pop().replace(/\.\w+$/, '') // 全局注册组件 Vue.component( componentName, componentConfig.default || componentConfig ) })</code>
4. vue 子组件独立打包
上面的【方式3】解决了【方式2】显式指定全部组件的不便,但动态组件仍需要和主项目一起打包,在一些场景下则显得不便,最理想的状态应该是:主程序和子组件独立打包,能够根据异步接口的返回结果动态地加载组件。
4.1 webpack + vue-loader
webpack 中 vue 子组件独立打包,需要使用对应的 vue-loader
识别 vue 文件,见 01-webpack
,可以参考 vue-loader
文档,vue-loader文档地址,需要配置 webpack.config.js
<code class="language-javascript">{ test: /\.vue$/, loader: 'vue-loader' }</code>
构建的组件的使用
<code class="language-html"><!-- 动态组件 --> <div :is="compName"></div></code>
动态载入 build 后的 bundle
<code class="language-javascript">loadScript('xxx').then(() => { Vue.component(compName, window[compName].default); this.compName = compName; })</code>
4.2 vue-cli
vue 官方提供的脚手架 vue-cli
集成了 vue-loader
,开箱即用,可以轻松实现独立入口打包。见 02-vue-lib
,参考 vue-cli
文档,vue-cli 文档地址:
<code class="language-shell"># 将一个单独的入口构建为一个库 $ vue-cli-service build --target lib --name myLib [entry]</code>
4.3 导入动态组件脚本文件
经过打包的组件可以生成 js 脚本文件或 lib 库文件,可以根据接口等方式的返回结果,通过 script
或 import
的方式引入,见 03-vue-lib
<code class="language-vue">loadScript('http://xxx/xx.js').then(() => { Vue.component(compName, window[compName].default || window[compName]); this.compName = compName; })</code>
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#应用)