likes
comments
collection
share

前端微组件之Vue2动态加载异步组件

作者站长头像
站长
· 阅读数 46

前言

去年工作中,有幸参与了公司业务拆分,公司业务繁杂,同个业务模块代码散落在多个业务系统中,并且随着公司发展各个业务系统又下放到不同的部门,有些部门的业务就需要跨部门协作;那需要跨部门的业务,一般是业务方提供组件库,协作方在他们系统完成对应业务的接入。一般来说这样的合作模式是可行的,但存在某些情况,这样的合作模式无法达到快速迭代的效果、加大沟通成本、项目上线周期长。例如:一个收集信息的业务(可能是一个弹窗组件),在多条业务线都涉及,每次收集信息的业务进行迭代,都需要通知业务线系统更新组件库版本,假如公司的上线模式在发布阶段有灰度时间计划,那这种收集信息的业务需要依赖宿主系统,采用npm依赖包的模式,就非常的CaoDan,你不得不去和对应业务线借系统制定灰度时间计划,碰巧人家系统在你的计划的灰度时间内正在使用,那你的项目大概率就得延期了。为了兼容这种业务场景和上线模式,组内搞出了一种基于异步组件的方案。

方案的落地

业界有很多微应用的案例,qiankun、single-spa、micro-app...等等都非常不错,但还是有一定的学习成本和改造成本,所以没使用,但借鉴了它们的思路,我们设计了一套基于Vue2异步组件的简易版的微组件加载器。

前端微组件之Vue2动态加载异步组件

微组件加载器的作用是请求组件资源、加载组件资源;

Vue加载异步组件

vue提供了异步的方式来加载组件,异步组件

new Vue({  
    // ...  
    components: {  
        'my-component-a': () => import('./my-async-component-a')  
        'my-component-b': () => ({
            // 需要加载的组件 (应该是一个 `Promise` 对象)  
            component: import('./MyComponentB.vue'),
            // 异步组件加载时使用的组件  
            loading: LoadingComponent,
            // 加载失败时使用的组件  
            error: ErrorComponent,  
            // 展示加载时组件的延时时间。默认值是 200 (毫秒)  
            delay: 200,  
            // 如果提供了超时时间且组件加载也超时了,  
            // 则使用加载失败时使用的组件。默认值是:`Infinity`  
            timeout: 3000
        }) 
    }  
})

针对Vue的业务工程,我们可以尝试基于异步组件的方式,以最小的改动来实现公共业务组件代码抽离出各个业务系统。示例代码如下:

<template>
  <div>
    <ComponentA />
  </div>
</template>

<script>
import MicroComponentLoader from "MicroComponentLoader";
import LoadingComponent from "./LoadingComponent";
import LoadingComponent from "./ErrorComponent";
export default {
  components: {
    ComponentA: MicroComponentLoader.get('ComponentA'),
    ComponentB: () => ({
            component: MicroComponentLoader('ComponentB'),
            loading: LoadingComponent,
            error: ErrorComponent,
            timeout: 3000
        }) 
  }
};
</script>

<style>
</style>

MicroComponentLoader 就是上述提到的微组件加载器,其原理这里不赘述,之前给女朋友讲解异步组件后,她详细的讲了MicroComponentLoader的实现方式,有兴趣可以看看微组件加载器原理与代码实现(需要科学上网--)。其原理很简单,通过组件参数,获取对应的组件js/css资源,再动态创建script去挂载js/css,拿到组件对象,再通过script的load事件回调获取挂载在window上的组件对象,再将其返回给业务系统去注册组件。

Vue动态加载异步组件

上面大篇幅讲了微组件的方案,但实际落地,发现每个业务组件的注册都需要声明式的写错误兜底组件/加载效果组件/超时时间,当一个页面有多个异步组件,那写起来相当繁琐。

我们就在想,是否可以通过一个高阶组件来封装异步组件,实现动态组件加载。

Vue2的组件注册

组件注册,Vue官网提供了全局组件注册/局部组件注册/异步组件注册的文档,如果我们想动态注册异步组件: 按照官网的方式,用Vue.component(),进行全局注册

function registerComponent(name, Vue) {
    Vue.component(name, () => ({
        component: MicroComponentLoader(name),
        loading: LoadingComponent,
        error: ErrorComponent,
        timeout: 3000
    }))
}

但我们在工程系统中异步注册组件,一般都是局部注册,当我们试图在options.components获取当前组件实例:

<script>
export default {
    components: {
        componentOne: () => {
            console.log(this) // 报错,this不存在
            return {
                component: componentOne
            }
        }
    }
}
</script>

这是因为options.components是保存在VueComponent.options.components上的,组件实例化过程中,并不会把components挂载在组件实例上。那是否有方式可以进行动态注册局部组件呢?

答案是有的, 接下来就从Vue注册异步组件的方式逐步寻找动态注册局部组件的方法。

Vue的异步组件注册原理

解析template字符串,发现当前解析的标签是组件的话,会先获取组件的options.components[组件名]的子组件信息,调用createComponent方法,

前端微组件之Vue2动态加载异步组件 createComponent方法是在core/vdom/create-component.js文件中定义的,异步组件会先调用resolveAsyncComponent方法试图获取组件构造函数,获取不到,则会创建一个占位节点,等到组件数据返回之后,再将占位节点替换成组件节点。

前端微组件之Vue2动态加载异步组件

我们看下resolveAsyncComponent方法是怎么实现组件注册的,如下图,前面的各种判断我们本文就不展开讲了,只关注resolve方法,里面调用了ensureCtor,并把组件数据和baseCtor当做参数传入,baseCtor是构造函数Vue前端微组件之Vue2动态加载异步组件

ensureCtor做了什么呢? 前端微组件之Vue2动态加载异步组件 判断当前组件数据是否为对象,是对象,返回base.extend(comp)extend大家应该都不陌生,其作用是创建一个VueComponent构造函数。

前端微组件之Vue2动态加载异步组件 我们可以看到,resolveAsyncComponent方法最终是返回一个组件构造函数。

好了,现在我们知道options.components中异步组件其实最开始也是一个组件构造函数,那我们思考一下,是否可以尝试在组件的options.components动态塞入子组件数据?答案是:必须是可行的!!!

动态加载异步组件的实现

export default {
    created() {
        this.constructor.component('组件名', () => ({
            component: MicroComponentLoader('ComponentB'),
            loading: LoadingComponent,
            error: ErrorComponent,
            timeout: 3000
        }))
    }
}

如上述代码,我们在组件生命周期的created内,调用组件构造函数的component方法,来给组件的options.components中注入一个异步组件。component方法是怎么实现的呢?在extend方法中,有这样一段代码:

前端微组件之Vue2动态加载异步组件

const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

它将父组件构造函数的上述三个方法挂载到子组件构造函数。而最初的父组件构造函数就是Vue,Vue.componentVue执行之初,调用了initAssetRegisters(Vue)方法实现的,在core/global-api/assets.js文件中定义了initAssetRegisters方法。 前端微组件之Vue2动态加载异步组件 component方法执行的时候,会在构造函数的options.components对象上挂载子组件的构造函数。

至此,我们在组件生命周期的created内,调用组件构造函数的component方法,来给组件注册子组件的方式是可行的。

示例组件展示

前端微组件之Vue2动态加载异步组件

<template>
    <div>
        <AsyncComp />
        <component v-bind="$attrs" v-on="$listeners" :is="componentName"/>
    </div>
</template>

<script>
import ComponentOne from '../component-one';
import LoadingComp from '../loading-comp';
export default {
    name: 'MicroComp',
    props: {
        componentName: {
            type: String,
            required: true
        }
    },
    components: {
        // 对照组件(用这个和通过this.constructor.component的方式创建的组件有何异同)
        AsyncComp: () => import('../async-comp/index.vue')
    },
    created() {
        this.constructor.component(this.componentName, () => {
            // 这里模拟异步组件,实际场景,是使用微组件加载器去获取组件配置对象
            const resolve = new Promise((_resolve) => {
                setTimeout(() => _resolve(ComponentOne), 3000)
            })
            return {
                component: resolve,
                loading: LoadingComp
            }
        })
    }
}
</script>
<template>
  <div id="app">
    <MicroComponent componentName="ComponentOne"/>
  </div>
</template>

<script>
import MicroComponent from './components/micro-comp/index';

export default {
  name: 'App',
  components: {
    MicroComponent
  }
}
</script>

<style>
</style>

我们通过组件实例对象,可以看到,this.constructor.component注册的子组件已经挂载到组件构造函数的options.components上了,并且正常渲染,数据也是响应式的。 后续就是围绕动态注册异步组件的兜底处理,例如异步组件资源加载失败/超时做日志上报、超时时间过后,加载兜底组件等等逻辑了。

小结

为了实现异步组件的统一兜底组件,不得不翻源码,找可实现方式。该看源码的时候,还真得看源码,有些使用方式使用场景,官方文档可能不会详细介绍,一切都需要自己探索。给自己打打气加加油。

文章若有错误,或者该方式有何不妥,请指正。😗

转载自:https://juejin.cn/post/7249299811497017399
评论
请登录