【问题标题】:Dynamic Vue components with sync and events具有同步和事件的动态 Vue 组件
【发布时间】:2017-11-29 22:22:11
【问题描述】:

我在 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。这可能吗?我欢迎任何建议!

【问题讨论】:

    标签: vue.js vue-component


    【解决方案1】:

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

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

    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 方法会查看modules 中的template 中的所有属性,以决定如何渲染组件。在属性的情况下,它只是传递它们。对于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>

    【讨论】:

    • 谢谢!我之前没有做过自定义渲染功能,所以我要阅读大量内容。它似乎比自定义指令更有意义。我会试一试,看看效果如何!
    • 再次感谢,看来这绝对为我指明了正确的方向。它在技术上回答了我的问题,尽管我预见到未来会出现一些并发症。由于我打算将它与vuedraggable 一起使用,我将把这个自定义渲染的组件包装在draggable 中,但是所有的变量、回调等都将在父级的范围内。关于如何以不凌乱的方式使用它们的任何想法? (在这种情况下,将每个 var 作为离散道具传递感觉“混乱”,我不知道如何处理这里的事件)。有什么想法吗?
    • @DMack 使用v-bind="someObject" 可以相对容易地完成传递多个变量,其中someObject 是一个对象,其中键是组件的属性,值是您要传递的内容。
    • v-on 处理程序怎么样?有什么方法可以捕获任何事件并将其重新发送给父母?
    • @DMack 你的结构是parent -> draggable -> some-component -> other components,你想从other-components -> parent传递事件?这可能是您想使用公共汽车的情况。
    猜你喜欢
    • 2018-08-07
    • 2020-10-24
    • 2020-12-03
    • 2017-04-13
    • 2021-12-27
    • 2020-02-22
    • 2017-12-19
    • 2019-10-15
    • 2019-02-07
    相关资源
    最近更新 更多