简体   繁体   English

高级 Vue.js 动态功能组件使用 `:is` 语法并渲染 function

[英]Advanced Vue.js Dynamic Functional Component using `:is` syntax and render function

Background: I've built a standard single file component that takes a name prop and looks in different places my app's directory structure and provides the first matched component with that name.背景:我已经构建了一个标准的单文件组件,它采用name道具并在不同位置查看我的应用程序目录结构并提供具有该名称的第一个匹配组件。 It was created to allow for "child theming" in my Vue.js CMS, called Resto.创建它是为了在我的 Vue.js CMS(称为 Resto)中允许“儿童主题化”。 It's a similar principle to how WordPress looks for template files, first by checking the Child theme location, then reverting to the parent them if not found, etc.这与 WordPress 查找模板文件的原理类似,首先检查子主题位置,如果找不到则恢复到父主题,等等。

Usage : The component can be used like this:用法:该组件可以这样使用:

<!-- 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> 

My goal : I want to convert to a functional component so it doesn't affect my app's render performance or show up in the Vue devtools.我的目标:我想转换为功能组件,这样它就不会影响我的应用程序的渲染性能或出现在 Vue 开发工具中。 It looks like this:它看起来像这样:

<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>

I've tried SO many different approaches but I'm fairly new to functional components and render functions (never got into React).我尝试了很多不同的方法,但我对函数式组件和渲染函数还很陌生(从未接触过 React)。

The roadblock : I can't seem to figure out how to run the chained functions that I call in my original mounted() function.障碍:我似乎无法弄清楚如何运行我在原始mounted() function 中调用的链式函数。 I've tried running it from inside the render function with no success.我尝试从渲染 function 内部运行它,但没有成功。

Big Question大问题

How can I find and dynamically import the component I'm targeting before I pass that component to the createElement function (or within my single file <template functional><template/> )?在将该组件传递给createElement function (或在我的单个文件<template functional><template/>中)之前,如何找到并动态导入我定位的组件?

Thanks all you Vue-heads!谢谢你们所有的 Vue 负责人! ✌️ ✌️

Update : I stumbled acrossthis solution for using the h() render function and randomly loading a component, but I'm not sure how to make it work to accept the name prop...更新:我偶然发现了这个使用h()渲染 function 并随机加载组件的解决方案,但我不知道如何让它接受name prop...

Late to the party, but I was in a similar situation, where I had a component in charge of conditionally render one of 11 different child components:聚会迟到了,但我遇到了类似的情况,我有一个组件负责有条件地渲染 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>

which of course is not ideal and pretty ugly.这当然不理想而且很丑陋。

The functional equivalent I came up with is the following我想出的功能等价物如下

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 } })
  }
}

Couple of things:几件事:

  • lazy imports don't seem to work inside Vue.component, hence require().default is the way to go惰性导入似乎在 Vue.component 中不起作用,因此 require().default 是 go 的方式
  • in this case the prop 'type' needs to be formatted, either in the parent component or right here在这种情况下,需要在父组件中或此处对道具“类型”进行格式化

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM