Vue js 动态导入组件

Posted

技术标签:

【中文标题】Vue js 动态导入组件【英文标题】:Vue js import components dynamically 【发布时间】:2020-01-24 14:13:28 【问题描述】:

我有以下父组件,它必须呈现动态子组件列表:

<template>
  <div>
    <div v-for="(componentName, index) in supportedComponents" :key="index">
      <component v-bind:is="componentName"></component>
    </div>
   </div>
 </template>

<script>
const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");
export default 
  name: "parentComponent",
  components: 
    Component1,
    Component2
  ,
  props: 
    supportedComponents: 
      type: Array,
      required: true
    
  
;
</script>

supportedComponents 属性是我想在父组件中呈现的组件名称列表。

为了在父组件中使用子组件,我必须导入它们并注册它们。

但唯一的方法是硬编码组件的导入路径:

const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");

然后像这样注册它们:

components: 
  Component1,
  Component2

我想让我的parentComponent 尽可能通用。这意味着我必须找到一种方法来避免在导入语句和注册时硬编码组件路径。我想向parentComponent 注入它应该导入和渲染的子组件。

这在 Vue 中可行吗?如果是,那么如何?

【问题讨论】:

你能把组件作为道具传递吗?但是在某个地方你必须静态导入组件。 import()是在编译时解析的,所以需要在编译时拥有。 【参考方案1】:

您可以在 created 生命周期内加载组件并根据您的数组属性注册它们:

<template>
    <div>
        <div v-for="(componentName, index) in supportedComponents" :key="index">
            <component :is="componentName"></component>
        </div>
    </div>
</template>

<script>
    export default 
        name: "parentComponent",
        components: ,
        props: 
            supportedComponents: 
                type: Array,
                required: true
            
        ,
        created ()  
            for(let c=0; c<this.supportedComponents.length; c++) 
                let componentName = this.supportedComponents[c];
                this.$options.components[componentName] = () => import('./' + componentName + '.vue');
            
        
    ;
</script>

效果不错

【讨论】:

我收到Failed to resolve async component..., Reason: Error: Cannot find module '@/components/General/TextLink.vue'。所以它找不到模块,但这是正确的路径。 在没有我的脚本的情况下导入是否有效?如果不是,那可能是另一个问题。 只是为了澄清@Launemax 的答案,导入语句必须是string literal。所以import(componentName) 将产生“无法解析异步组件...”错误。而 import('./' + componentName + '.vue') 会起作用 在我的情况下,我收到“无法安装组件:未定义模板或渲染功能”。正常导入时组件工作正常。 this.$options.components 的 console.log 显示组件已注册。路径三重和四重检查。截图:dl.rowild.at/vue-component-error.png - 有什么想法吗?【参考方案2】:

在运行时解析动态 webpack import()

您可以动态设置import() 函数的路径,以根据组件状态加载不同的组件。

<template>
  <component :is="myComponent" />
</template>

<script>
export default 
  props: 
    component: String,
  ,

  data() 
    return 
      myComponent: '',
    ;
  ,

  computed: 
    loader() 
      return () => import(`../components/$this.component`);
    ,
  ,

  created() 
    this.loader().then(res => 
      // components can be defined as a function that returns a promise;
      this.myComponent = () => this.loader();
    ,
  ,

</script>

注意: javascript 在运行之前由您的浏览器编译。这与如何解决 webpack 导入无关。

【讨论】:

这不起作用,我得到 runtime-core.esm-bundler.js?5c40:38 [Vue 警告]:无效的 VNode 类型:未定义(未定义) 您使用的是默认导出吗?如果您使用命名导出,您可以尝试() =&gt; import(...).&lt;name&gt; 这样动态加载的时候还需要注册组件吗?我发现它们无法正确显示,直到您将动态导入放入组件中: 对象。还必须添加defineAsyncComponent()【参考方案3】:

这是一个工作代码,只要确保你的动态导入中有一些字符串,否则你会得到“找不到模块”

       <component :is="current" />
    export default   data () 
    return 
      componentToDisplay: null
    
  ,
  computed: 
    current () 
      if (this.componentToDisplay) 
        return () => import('@/components/notices/' + this.componentToDisplay)
      
      return () => import('@/components/notices/LoadingNotice.vue')
    
  ,
  mounted () 
    this.componentToDisplay = 'Notice' + this.$route.query.id + '.vue'
  

【讨论】:

很好的答案,我在各种论坛、vue 网站等上查看了几十个解决这个问题的方法,但没有一个比这个清晰或好。我无法想象在不遇到这个问题的情况下构建一个复杂的 Vue 站点。在我看来,这是在 Vue 结构中动态加载组件的最佳方式。还有其他方法可以做到这一点 - 但它们都感觉像黑客。【参考方案4】:

我认为我们需要一些插件,它可以包含代码并且每次都应该自动加载。这个解决方案对我有用。

import  App, defineAsyncComponent  from 'vue'
const componentList = ['Button', 'Card']
export const registerComponents = async (app: App): void => 
  // import.meta.globEager('../components/Base/*.vue')
  componentList.forEach(async (component) => 
    const asyncComponent = defineAsyncComponent(
      () => import(`../components/Base/$component.vue`)
    )
    app.component(component, asyncComponent)
  )

你也可以试试 glob,它也很好用,但我已经检查过这个解决方案,但看看这个值得一读

Dynamic import

[更新]

我对 import.meta.globEage 进行了同样的尝试,它的工作原理只是加载有点延迟,您可能会觉得它加载缓慢但并不明显。

import  App, defineAsyncComponent  from 'vue'
export const registerComponents = async (app: App): void => 
Object.keys(import.meta.globEager('../components/Base/*.vue')).forEach(
    async (component) => 
    const asyncComponent = defineAsyncComponent(
        () => import(/* @vite-ignore */ component)
    )
    app.component(
        (component && component.split('/').pop()?.split('.')[0]) || '',asyncComponent
    )
    )

【讨论】:

以上是关于Vue js 动态导入组件的主要内容,如果未能解决你的问题,请参考以下文章

使用自动注册的动态导入的 Vue 组件进行代码拆分

vue + webpack:动态组件导入是如何工作的?

组件动态导入在 router.js 中不起作用

批量自动化注册全局组件(Vue3)

Vuejs 中的动态导入

vue 通过 component 动态渲染组件