Vue.js将数据从插槽传递到组件实例

7
我正在尝试使用Vue.js构建可重复使用的组件,以处理通过AJAX提交表单的问题。理想情况下,我想要一个通用组件,可以用来替换HTML的form元素,其中可能包含一组未知的表单元素,如inputselecttextarea等。
以下是我为名为ajax-form的组件编写的代码:
<template>
    <form class="form" :action="action" :method="method" v-on:submit.prevent="ajaxSubmit">
        <slot></slot>
    </form>
</template>

<script>
module.exports = {
    props: {
        action: {
            required: true,
            type: String
        },
        method: {
            default: 'post',
            required: false,
            type: String
        }
    },
    data() {
        return {
            formData: {}
        }
    },
    methods: {
        ajaxSubmit() {
            // Do ajax
        }
    }
}
</script>

在我的HTML中,我会有以下内容:

而在我的HTML中,我会有类似以下的内容:

<ajax-form action="http://example.com/do/something">
    <input name="first_name" type="text">
    <textarea name="about_you"></textarea>
</ajax-form>

我希望发生的理想情况是,我在组件内使用插槽放置所有表单元素,这些元素应该被映射到Vue组件实例中的data.formData属性。因此,在这种情况下,data属性看起来像:

data: {
    formData: {
        first_name: '',
        about: ''
    }
}

如果我要在HTML组件中添加另一个字段,我期望它也映射到Vue实例的data属性中。 是否有办法做到这一点?是否有一种方法可以告诉Vue,在通过插槽将我的表单元素放入组件时,我希望该元素映射到组件的data中的某个内容? 我已经尝试为每个表单元素添加v-modelv-bind,以查看是否可以将数据传递给组件的数据:
<ajax-form action="http://example.com/do/something">
    <input name="first_name" type="text" v-model="formData.first_name">
</ajax-form>

然而,Vue 抱怨在模板中使用响应式数据属性之前必须声明它们:

[Vue 警告]: 实例上没有定义 "formData" 属性或方法,但是在渲染时引用了它。请确保在 data 选项中声明响应式数据属性。


只是随便想想:我在想是否可以使用 mounted 方法(https://vuejs.org/v2/api/#mounted)获取到 Vue 创建的 DOM 元素的引用,然后通过迭代它的子元素(使用 jQuery?),并通过 Vue.set(https://vuejs.org/v2/api/#Vue-set)向 this.data 添加响应式属性。 - PatrickSteele
请查看这个代码笔记 http://codepen.io/anon/pen/evYzOv?editors=1011 - Shekhar Joshi
谢谢大家的建议。@PatrickSteele 我尝试了你的建议,但没有进展。我开始觉得这是不可能的。文档似乎在这里说明了:https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties。@ShekharJoshi 我不确定你是否理解我的问题。我无法提前知道插槽的内容,我希望能够在通过插槽传递到我的组件中的元素和我的data项目之间创建反应绑定。您的建议假设我提前知道数据等内容。 - Jonathon
4个回答

34

您可以使用作用域插槽(scopedSlots)来实现此功能。

其API如下:

<ajax-form action="http://example.com/do/something">
   <template scope="{formData}">    
   <input name="first_name" type="text" v-model="formData.first_name">
   </template>
</ajax-form>

ajax-form组件中:

<form class="form" :action="action" :method="method" v-on:submit.prevent="ajaxSubmit">
   <slot :formData="formData">
</form>

3
非常有帮助!谢谢。 - Michael Giovanni Pumo
12
这应该是被接受的答案!这真的帮助我了解了作用域插槽,因为文档没有给出任何真实世界的例子。还要注意,自 Vue 2.5 以来,scope属性已经被弃用,slot-scope代替它,并且可以用于任何元素,而不仅仅是template。所以在这个例子中,你可以使用<input name="first_name" type="text" v-model="formData.first_name" slot-scope="{ formData }">代替将输入框包在模板标签中。 - spikyjt
2
非常感谢。但是Vue 2.6已经更新了语法,使用作用域插槽。 - Andrew Li

4

由于插槽的作用域仅限于父级作用域,因此您无法执行所需操作。

您可以在全局Vue实例上定义formData,因此现在可以从父级作用域访问它。

const app = new Vue({
    el: '#app',
    data: {
        formData : {}
    }
});

现在您可以将其作为属性传递给表单:
<ajax-form action="#" :form-data="formData>
    <input name="first_name" type="text" v-model="formData.first_name">
</ajax-form>

太好了。我感觉Vue不会让我按照我想要的方式仅通过组件传递插槽中的数据来完成,但这是一个不错的解决方法。 - Jonathon
这个问题也可以通过使用作用域插槽来很好地解决,而不需要任何黑科技。如blockhead的答案所述。 - exmaxx

2
您可以通过以下方式将数据作为参数从父组件传递给子组件

在父组件中:

<child-component ref="child">
      <h1 slot="heading">Test passing data to child</h1>
      <form slot="content">
          <input type="text" v-model="name" placeholder="Type your name" class="half">
          <input type="text" v-model="email" placeholder="Type your email" class="half">
          <div class="actions">
              <button data-role="submit" @click.prevent="callAddRep($event)">Add</button>
          </div>
      </form>
</child-component>

data() {
    return {
        name: '',
        email: ''
    }
},
methods: {
    callAddRep: function(e) {

      let name = this.name,
          email = this.email

      this.$refs.child.addRep(e, {
          name,
          email
      })
  }

在孩子中:
methods: {
      addRep: function(e, data) {

          //Data passed successfully
          console.log(data)

      }
  }

0

虽然@blockhead的答案是正确的,但重要的是要注意,无论将插入ajax-form的插槽是否为具有自己模板标记的单个文件组件,您仍然必须将该组件包装在外部模板标记中,以便拦截插槽属性,然后将它们作为属性传递到实际目标组件中。

<modal-two modal_id="notif-modal">

    <template #default="{id_for_children}">

        <notification-alert :parent_modal_name="id_for_children" />
    </template>
</modal-two>

我在其他地方没有看到过这个


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接