具有同步和事件的动态 Vue 组件

Dynamic Vue components with sync and events

我在 Vue.js 2.3 中使用 <component v-for="..."> 标签来动态呈现组件列表。

模板如下所示:

<some-component v-for="{name, props}, index in modules" :key="index">
    <component :is="name" v-bind="props"></component>
</some-component>

modules 数组在我的组件 data() 中:

modules: [
    {
        name: 'some-thing',
        props: {
            color: '#0f0',
            text: 'some text',
        },
    },
    {
        name: 'some-thing',
        props: {
            color: '#f3f',
            text: 'some other text',
        },
    },
],

我正在使用 v-bind={...} 对象语法来动态绑定道具,这非常有效。我还想通过这种方法将事件侦听器与 v-on 绑定(并使用 .sync'd 道具),但我不知道是否可以不创建自定义指令。

我试过像这样添加到我的 props 对象,但没有成功:

props: {
    color: '#f3f',
    text: 'some other text',
    'v-on:loaded': 'handleLoaded', // no luck
    'volume.sync': 'someValue', // no luck
},

我的目标是让用户使用 vuedraggable 对侧边栏中的小部件进行重新排序,并将他们的布局偏好保存到数据库中,但一些小部件有 @events 和 .synced props。这可能吗?我欢迎任何建议!

我不知道有什么方法可以使用动态组件完成此操作。但是,您可以使用渲染函数来完成。

考虑这个数据结构,它是你的修改。

modules: [
  {
    name: 'some-thing',
    props: {
      color: '#0f0',
      text: 'some text',
    },
    sync:{
      "volume": "volume"
    },
    on:{
      loaded: "handleLoaded"
    }
  },
  {
    name: 'other-thing',
    on:{
      clicked: "onClicked"
    }
  },
],

这里我定义了另外两个属性:synconsync 属性 是一个对象,其中包含您想要 sync 的所有属性的列表。例如,上面的 sync 属性 的其中一个组件包含 volume: "volume"。这表示您通常希望添加为 :volume.sync="volume" 的 属性。没有办法(据我所知)你可以动态地将它添加到你的动态组件中,但是在渲染函数中,你可以将它分解成它的脱糖部分并添加一个 属性 和一个处理程序 updated:volume.

on 属性 类似,在渲染函数中,我们可以为由调用值中标识的方法的键标识的事件添加处理程序。这是该渲染函数的可能实现。

render(h){
  let components = []
  let modules = Object.assign({}, this.modules)
  for (let template of this.modules) {
    let def = {on:{}, props:{}}
    // add props
    if (template.props){
      def.props = template.props
    } 
    // add sync props
    if (template.sync){
      for (let sync of Object.keys(template.sync)){
        // sync properties are just sugar for a prop and a handler
        // for `updated:prop`. So here we add the prop and the handler.
        def.on[`update:${sync}`] = val => this[sync] = val
        def.props[sync] = this[template.sync[sync]]
      }
    }
    // add handers
    if (template.on){
      // for current purposes, the handler is a string containing the 
      // name of the method to call
      for (let handler of Object.keys(template.on)){
        def.on[handler] = this[template.on[handler]]
      }
    }
    components.push(h(template.name, def))
  }
  return h('div', components)
}

基本上,render 方法会查看 modulestemplate 中的所有属性,以决定如何呈现组件。对于属性,它只是传递它们。对于 sync 属性,它将其分解为 属性 和事件处理程序,对于 on 处理程序,它添加了适当的事件处理程序。

这里是这个工作的一个例子。

console.clear()

Vue.component("some-thing", {
  props: ["volume","text","color"],
  template: `
    <div>
     <span :style="{color}">{{text}}</span>
      <input :value="volume" @input="$emit('update:volume', $event.target.value)" />
      <button @click="$emit('loaded')">Click me</button>
    </div>
  `
})

Vue.component("other-thing", {
  template: `
    <div>
      <button @click="$emit('clicked')">Click me</button>
    </div>
  `
})

new Vue({
  el: "#app",
  data: {
    modules: [{
        name: 'some-thing',
        props: {
          color: '#0f0',
          text: 'some text',
        },
        sync: {
          "volume": "volume"
        },
        on: {
          loaded: "handleLoaded"
        }
      },
      {
        name: 'other-thing',
        on: {
          clicked: "onClicked"
        }
      },
    ],
    volume: "stuff"
  },
  methods: {
    handleLoaded() {
      alert('loaded')
    },
    onClicked() {
      alert("clicked")
    }
  },
  render(h) {
    let components = []
    let modules = Object.assign({}, this.modules)
    for (let template of this.modules) {
      let def = {
        on: {},
        props: {}
      }
      // add props
      if (template.props) {
        def.props = template.props
      }
      // add sync props
      if (template.sync) {
        for (let sync of Object.keys(template.sync)) {
          // sync properties are just sugar for a prop and a handler
          // for `updated:prop`. So here we add the prop and the handler.
          def.on[`update:${sync}`] = val => this[sync] = val
          def.props[sync] = this[template.sync[sync]]
        }
      }
      // add handers
      if (template.on) {
        // for current purposes, the handler is a string containing the 
        // name of the method to call
        for (let handler of Object.keys(template.on)) {
          def.on[handler] = this[template.on[handler]]
        }
      }
      components.push(h(template.name, def))
    }
    return h('div', components)
  },
})
<script src="https://unpkg.com/vue@2.2.6/dist/vue.js"></script>
<div id="app"></div>