在Vue3中,如何在通过v-for循环创建的子组件内调用函数?

huangapple go评论51阅读模式
英文:

How to call a function inside a child component in `Vue3` created through a `v-for` loop?

问题

我目前正在使用vue3的composition API构建一个表单构建器。用户可以在保存表单之前添加不同类型的输入,比如文本、单选按钮等。保存的表单将根据相应的HTML输入进行呈现。用户可以编辑问题的名称,例如Company Name <HTML textInput

目前,当用户添加一个输入类型,例如文本时,该类型会保存在一个有序数组中。我通过有序数组运行一个v-for,创建一个自定义组件formComponent,并传递类型。

我的formComponent会呈现一个基本的文本输入,以便用户编辑问题的名称,以及一个用于显示文本输入的占位字符串。我的问题在于尝试从父组件中保存问题文本。

<div v-if="type=='text'">
    <input type="text" placeholder="输入标题" /> 
    <span>输入字段在这里</span>
</div>

在父文件中,我有一个exportForm按钮,按下该按钮应该理想地返回所有子组件的toString表示的有序数组。我尝试过使用$emit,但是无法触发从父组件到所有子组件的$emit,如果我理解正确,$emit是设计用于父组件监听子事件的。

我还尝试在v-for中使用$refs。但是,当我记录$refs时,它们给我返回的是div元素。

<div v-for="item in formItems" ref="formComponents">
   <FormComponent :type="item" />
</div>

理想的解决方案将是在每个子组件中定义一个toString()方法,并通过循环遍历组件数组来调用toString(),然后将其附加到一个字符串中,但我无法做到这一点。

任何建议将不胜感激!

英文:

I am currently building a form builder with vue3 composition API. The user can add in different types of inputs like text, radio buttons etc into the form before saving the form. The saved form will then render with the appropriate HTML inputs. The user can edit the name of the question, eg Company Name &lt;HTML textInput.

Currently, when the user adds an input type eg,text, the type is saved into an ordered array. I run a v-for through the ordered array and creating a custom component formComponent, passing in the type.

My formComponent renders out a basic text input for the user to edit the name of the question, and a place holder string for where the text input will be displayed. My issue is in trying to save the question text from the parent.

    &lt;div v-if=&quot;type==&#39;text&#39;&quot;&gt;
        &lt;input type=&quot;text&quot; placeholder=&quot;Key in title&quot;/&gt; 
        &lt;span&gt;Input field here&lt;/span&gt;
    &lt;/div&gt;

I have an exportForm button in the parent file that when pressed should ideally return an ordered array of toString representations of all child components. I have tried playing with $emit but I have issue triggering the $emit on all child components from the parent; if I understand, $emit was designed for a parent component to listen to child events.

I have also tried using $refs in the forLoop. However, when I log the $refs they give me the div elements.

&lt;div v-for=&quot;item in formItems&quot; ref=&quot;formComponents&quot;&gt;
   &lt;FormComponent :type=&quot;item&quot; /&gt;
&lt;/div&gt;

The ideal solution would be to define a method toString() inside each of the child components and have a forLoop running through the array of components to call toString() and append it to a string but I am unable to do that.

Any suggestions will be greatly appreciated!

答案1

得分: 2

At first:

首先:

You don't really need to access the child components, to get their values.

你实际上不需要访问子组件来获取它们的值。

You can bind them dynamically on your data. I would prefer this way, since it is more Vue conform way to work with reactive data.

你可以动态地将它们绑定到你的数据上。我更喜欢这种方式,因为这是更符合Vue的工作方式来处理响应式数据的方式。

But I have also implemented the other way you wanted to achieve, with accessing the child component's methods getValue().

但我也实现了你想要的另一种方式,通过访问子组件的方法 getValue()

I would not suggest to use toString() since it can be confused with internal JS toString() function.

我不建议使用 toString(),因为它可能会与内部的JS toString()函数混淆。

In short:

简而言之:

Here is the working playground with the both ways of achieving your goal.

以下是一个工作示例,展示了实现目标的两种方式。

Pay attention how the values are automatically changing in the FormItems data array.

请注意值在 FormItems 数据数组中是如何自动更改的。

英文:

At first:

You don't really need to access the child components, to get their values. You can bind them dynamically on your data. I would prefer this way, since it is more Vue conform way to work with reactive data.

But I have also implemented the other way you wanted to achieve, with accessing the child component's methods getValue().

I would not suggest to use toString() since it can be confused with internal JS toString() function.

In short:

  • the wrapping &lt;div&gt; is not necessary
  • the refs should be applied to the &lt;FormComponents&gt; (see Refs inside v-for)
  • this.$refs.formComponents returns the Array of your components
  • FormComponent is used here as &lt;form-components&gt; (see DOM Template Parsing Caveats)
  • The values are two-way bound with Component v-model

Here is the working playground with the both ways of achieving your goal.
Pay attention how the values are automatically changing in the FormItems data array.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const { createApp } = Vue;

const FormComponent = {
  props: [&#39;type&#39;, &#39;modelValue&#39;],
  emits: [&#39;update:modelValue&#39;],
  template: &#39;#form-component&#39;,
  data() { 
    return { value: this.modelValue }
  },
  methods: {
    getValue() {
      return this.value;
    }
  }
}

const App = { 
  components: { FormComponent },
  data() {
    return {
      formItems: [
        { type: &#39;text&#39;, value: null },
        { type: &#39;checkbox&#39;, value: false }
      ]
    }  
  },
  methods: {
    getAllValues() {
      let components = this.$refs.formComponents;
      let values = [];
      for(var i = 0; i &lt; components.length; i++) {
        values.push(components[i].getValue())
      }
      console.log(`values: ${values}`);
    }
  }
}
const app = createApp(App)
app.mount(&#39;#app&#39;)

<!-- language: lang-css -->

#app { line-height: 2; }
[v-cloak] { display: none; }
label { font-weight: bold; }
th, td { padding: 0px 8px 0px 8px; }

<!-- language: lang-html -->

&lt;div id=&quot;app&quot;&gt;
  &lt;label&gt;FormItems:&lt;/label&gt;&lt;br/&gt;
  &lt;table border=1&gt;
    &lt;thead&gt;&lt;tr&gt;&lt;th&gt;#&lt;/th&gt;&lt;th&gt;Item Type:&lt;/th&gt;&lt;th&gt;Item Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
    &lt;tbody&gt;&lt;tr v-for=&quot;(item, index) in formItems&quot; :key=&quot;index&quot;&gt;
      &lt;td&gt;{{index}}&lt;/td&gt;&lt;td&gt;{{item.type}}&lt;/td&gt;&lt;td&gt;{{item.value}}&lt;/td&gt;
      &lt;/tr&gt;&lt;/tbody&gt;
  &lt;/table&gt;  
  &lt;hr/&gt;
  &lt;label&gt;FormComponents:&lt;/label&gt;
  &lt;form-component 
    v-for=&quot;(item, index) in formItems&quot; 
    :type=&quot;item.type&quot; v-model=&quot;item.value&quot; :key=&quot;index&quot; ref=&quot;formComponents&quot;&gt;
  &lt;/form-component&gt;
  &lt;button type=&quot;button&quot; @click=&quot;getAllValues&quot;&gt;Get all values&lt;/button&gt;
&lt;/div&gt;
&lt;script src=&quot;https://unpkg.com/vue@3/dist/vue.global.prod.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/x-template&quot; id=&quot;form-component&quot;&gt;
  &lt;div&gt;
    &lt;label&gt;type:&lt;/label&gt; {{type}}, 
    &lt;label&gt;value:&lt;/label&gt; &lt;input :type=&#39;type&#39; v-model=&quot;value&quot; @input=&quot;$emit(&#39;update:modelValue&#39;, this.type==&#39;checkbox&#39; ? $event.target.checked : $event.target.value)&quot; /&gt;
  &lt;/div&gt;
&lt;/script&gt;

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年2月8日 18:09:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75384198.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定