1

背景:我已经构建了一个标准的单文件组件,它接受一个name道具并在不同的地方查看我的应用程序的目录结构,并提供具有该名称的第一个匹配的组件。创建它是为了在我的 Vue.js CMS 中允许“子主题”,称为 Resto。这与 WordPress 查找模板文件的原理类似,首先检查子主题位置,如果找不到,则将其还原到父主题,等等。

用法:该组件可以这样使用:

<!-- Find the PageHeader component
in the current child theme, parent theme,
or base components folder --->
<theme-component name="PageHeader">
    <h1>Maybe I'm a slot for the page title!</h1>
</theme-component> 

我的目标:我想转换为功能组件,这样它就不会影响我的应用程序的渲染性能或出现在 Vue 开发工具中。它看起来像这样:

<template>
  <component
    :is="dynamicComponent"
    v-if="dynamicComponent"
    v-bind="{ ...$attrs, ...$props }"
    v-on="$listeners"
    @hook:mounted="$emit('mounted')"
  >
    <slot />
  </component>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'ThemeComponent',
  props: {
    name: {
      type: String,
      required: true,
      default: '',
    },
  },
  data() {
    return {
      dynamicComponent: null,
      resolvedPath: '',
    }
  },
  computed: {
    ...mapGetters('site', ['getThemeName']),
    customThemeLoader() {
      if (!this.name.length) {
        return null
      }
      // console.log(`Trying custom theme component for ${this.customThemePath}`)
      return () => import(`@themes/${this.customThemePath}`)
    },
    defaultThemeLoader() {
      if (!this.name.length) {
        return null
      }
      // console.log(`Trying default component for ${this.name}`)
      return () => import(`@restoBaseTheme/${this.componentPath}`)
    },

    baseComponentLoader() {
      if (!this.name.length) {
        return null
      }
      // console.log(`Trying base component for ${this.name}`)
      return () => import(`@components/Base/${this.name}`)
    },

    componentPath() {
      return `components/${this.name}`
    }, // componentPath

    customThemePath() {
      return `${this.getThemeName}/${this.componentPath}`
    }, // customThemePath()
  },
  mounted() {
    this.customThemeLoader()
      .then(() => {
        // If found in the current custom Theme dir, load from there
        this.dynamicComponent = () => this.customThemeLoader()
        this.resolvedPath = `@themes/${this.customThemePath}`
      })
      .catch(() => {
        this.defaultThemeLoader()
          .then(() => {
            // If found in the default Theme dir, load from there
            this.dynamicComponent = () => this.defaultThemeLoader()
            this.resolvedPath = `@restoBaseTheme/${this.defaultThemePath}`
          })
          .catch(() => {
            this.baseComponentLoader()
              .then(() => {
                // Finally, if it can't be found, try the Base folder
                this.dynamicComponent = () => this.baseComponentLoader()
                this.resolvedPath = `@components/Base/${this.name}`
              })
              .catch(() => {
                // If found in the /components dir, load from there
                this.dynamicComponent = () => import(`@components/${this.name}`)
                this.resolvedPath = `@components/${this.name}`
              })
          })
      })
  },
}
</script>

我尝试了很多不同的方法,但我对函数式组件和渲染函数还很陌生(从未接触过 React)。

障碍:我似乎无法弄清楚如何运行我在原始函数中调用的链式mounted()函数。我试过从渲染函数内部运行它,但没有成功。

大问题

createElement在将组件传递给函数(或在我的单个文件中)之前,如何找到并动态导入目标组件<template functional><template/>

谢谢你们所有的 Vue 负责人!✌️

更新:我偶然发现了这个使用h()渲染功能并随机加载组件的解决方案,但我不知道如何让它接受name道具......

4

1 回答 1

0

聚会迟到了,但我遇到了类似的情况,我有一个组件负责有条件地渲染 11 个不同的子组件之一:

<template>
  <v-row>
    <v-col>
      <custom-title v-if="type === 'title'" :data="data" />
      <custom-paragraph v-else-if="type === 'paragraph'" :data="data" />
      <custom-text v-else-if="type === 'text'" :data="data" />
      ... 8 more times
    </v-col>
  </v-row>
</template>

<script>
export default {
  name: 'ProjectDynamicFormFieldDetail',
  components: {
    CustomTitle: () => import('@/modules/path/to/CustomTitle'),
    CustomParagraph: () => import('@/modules/path/to/CustomParagraph'),
    CustomText: () => import('@/modules/path/to/CustomText'),
    ... 8 more times
  },
  props: {
    type: {
      type: String,
      required: true,
    },
    data: {
      type: Object,
      default: null,
    }
  },
}
</script>

这当然不理想而且很丑陋。

我想出的功能等价物如下

import Vue from 'vue'

export default {
  functional: true,
  props: { type: { type: String, required: true }, data: { type: Object, default: null } },
  render(createElement, { props: { type, data } } ) {
    // prop 'type' === ['Title', 'Paragraph', 'Text', etc]
    const element = `Custom${type}`
    // register the custom component globally
    Vue.component(element, require(`@/modules/path/to/${element}`).default)
    return createElement(element, { props: { data } })
  }
}

几件事:

  • 惰性导入似乎在 Vue.component 中不起作用,因此 require().default 是要走的路
  • 在这种情况下,需要在父组件中或此处对道具“类型”进行格式化
于 2021-10-13T09:50:05.610 回答