【问题标题】:VueJS dynamic component subscribe to emit from child componentVueJS 动态组件订阅从子组件发出
【发布时间】:2019-09-24 01:28:10
【问题描述】:

我正在使用 Typescript 编写 VueJS (2.5.22) 应用程序并尝试在运行时动态添加组件。我遇到了 Typescript 的两个问题。添加子组件作为命名槽并订阅从子组件发出事件。我可以通过附加子组件来解决插槽,但是我想使用命名插槽。感谢您对插槽的任何输入。

我仍在尝试解决父组件订阅子事件和更新父组件中的属性的第二个问题。

子组件将发出“更新值”。如何在父组件上动态订阅这个事件?

谢谢,

请为:vuejs-component vuejs-dynamic-component vuejs-typescript vuejs-emit 创建标签

parent component adding dynamically at run-time in created method


<div>
    <p>Dynamic components</p>
    <div ref="controls"></div>
</div>

export default class ParentComponent extends Vue {

public $refs: Vue['$refs'] & {
    controls: HTMLElement
}
public $slots: Vue['$slots'] & {
    TextBox: TextBox
}

vuejs created method
--------------------

const labelControlContainerClass = Vue.extend(LabelControlContainer)
const textBoxClass = Vue.extend(TextBox)

const fields = table.fields
for (const key in fields) {
  if (fields.hasOwnProperty(key)) {
      const element = fields[key]
        if (element.fieldType === 'Char') {
          const textBoxInstance = new textBoxClass({
            // props
            propsData: {
              value: '',
              placeholder: element.translatedCaption,
              name: key
            },
            // how to subscript to "update-value" event???
          })
          textBoxInstance.$mount()

          const instance = new labelControlContainerClass({
            // props
            propsData: {
              caption: element.translatedCaption,
              bold: false
            },
          })

          instance.$mount()
          instance.$el.appendChild(textBoxInstance.$el) // add child component, try adding named slots, but didn't work
          this.$refs.controls.appendChild(instance.$el)
      }
  }
}

}
label component with slot. named slot didn't worked

<template>
  <div class="controlContainer" :class="{vertial: labelTop}">
    <span v-bind:class="{bold: bold}" class=".controlContainer__cisLabel">{{caption}}</span>
    <slot name='control'></slot>
    <slot></slot>
  </div>
</template>

<script lang='ts'>
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'

@Component({
})

export default class LabelControlContainer extends Vue {
  @Prop({ type: String, default: '' }) public caption: string
  @Prop({ type: Boolean, default: true }) public bold: boolean
  @Prop({ type: Boolean, default: false }) public labelTop: boolean
}

</script>

child component that is going to added to slot and emit on value change

export default class TextBox extends Vue {
  @Prop({ type: String, default: 'placeholder text' }) public placeholder: string
  @Prop({ type: Object, default: () => ({}) }) public attributes: any
  @Prop({ type: Boolean, default: false }) public readonly: boolean
  @Prop({ type: String, default: 'text' }) public mode: string
  @Prop({ type: Boolean, default: true }) public isValid: boolean
  @Prop({ type: String, default: '' }) public value: string
  @Prop({ type: String, default: '' }) public name: string
  @Prop({ type: Boolean, default: false }) public setFocus: boolean

  private default = {}
  private unlockable: boolean = this.readonly

  private valueChanged(data: any): void {
    this.$emit('update-value', data.value, this.name)
  }
}

【问题讨论】:

    标签: vue.js


    【解决方案1】:

    我没有使用 TypeScript,但我认为这与 TypeScript 无关,所以我将使用纯 JavaScript 来回答。

    要以编程方式添加slot 元素,您可以像这样使用$slots 属性:

    vm.$slots.default = ['Hello']
    vm.$slots. foo = ['Hello'] // for named slots
    

    ...但不幸的是,slots 必须是VNodes 的数组,而不是普通的 DOM 元素或 Vue 组件。您可以使用$createElement 方法来创建它们,如下所示:

    vm.$slots.default = [vm.$createElement('div', ['Hello'])]
    vm.$slots.default = [vm.$createElement('HelloWorld')] // custom component
    

    所以您的代码将如下所示:

    const TextBox = {
      template: `
        <div>{{ text }}</div>
      `,
      props: ['text']
    }
    
    const LabelControl = {
      template: `
        <div>
          <slot name='control'></slot>
        </div>
      `,
      components: {
        TextBox
      }
    }
    
    const LabelControlComponent = Vue.extend(LabelControl)
    
    new Vue({
      el: '#app',
      template: `
        <div>
          <div ref='controls'></div>
        </div>
      `,
      mounted () {
        let texts = ['a', 'b', 'c', 'd']
        texts.forEach(text => {
          let labelControl = new LabelControlComponent()
          labelControl.$slots.control = [
            labelControl.$createElement('TextBox', {
              props: { text }
            })
          ]
          labelControl.$mount()
          this.$refs.controls.appendChild(labelControl.$el)
        })
      }
    })
    

    slot 子级监听事件似乎是不可能的,因为它是不同的范围,但您仍然可以使用$parent 属性直接调用父级方法或从父级发出事件。

    const TextBox = {
      template: `
        <div @click='$parent.show(text)'>{{ text }}</div>
      `
    }
    
    const LabelControl = {
      methods: {
        show (text) {
          this.text = text
        }
      }
    }
    

    JSFiddle Example

    参考资料:

    【讨论】:

    • 感谢您的反馈。是的,您提供的插槽解决方案适用于 JavaScript,但是我在 Typescript 中遇到了类型问题。我可以通过 'public $slots: Vue['$slots'] & { TextBox: TextBox }' 扩展父插槽我如何扩展在运行时创建的组件?
    • 嗯不确定,但我认为它应该是VNode 的数组,可能是VNode[]。抱歉,我对 TypeScript 了解不多。
    猜你喜欢
    • 2021-02-12
    • 1970-01-01
    • 2018-09-30
    • 1970-01-01
    • 2020-10-06
    • 1970-01-01
    • 2018-09-30
    • 2021-04-22
    • 2015-05-23
    相关资源
    最近更新 更多