Home > Software design >  What is better to use to communicate with a child component - $emit or $ref?
What is better to use to communicate with a child component - $emit or $ref?

Time:01-27

Tell me, please. There is a page for changing user data. This page consists of fields for entering information about the user and a Submit button. I need to create a popup component that performs the same functions. The form itself will now be in a child component. The submit button is now moved to the parent component. Therefore, I need to pass the entered data to the parent component. Everything is complicated by the fact that initially data comes from the server with previously entered information about the user. Therefore, initially you need to transfer data to the child component through props, and then, when changing them from the child, transfer it to the parent. But what if there are a lot of variables? For example: I need to create 15 props and then pass this data through $emit. The data itself is passed either through the @input event of the <input> tag or the @change event of the <select> tag. I thought of three options, but I don't know which one is better. Or maybe some should not be used at all.

  1. Using $emit

Parent component

<template>
  <Child 
    :first-props="user.first"
    :second-props="user.second"
    ....
    :fifteenth-props="user.fifteenth"
  />
  <button @click='submit'>Submit</button>
</template>

<script>
import Child from '@/components/Child.vue'
import { mapGetters } from 'vuex'
export default {
  data: () => ({
    first: '',
    second: '',
    ...
    fifteenth: ''
  }),
  components: {
    Child
  },
  computed: {
    ...mapGetters({
      user: 'user/getUser'
    })
  },
  methods: {
    submit() {
     //Sending data
    }
  },
  mounted: {
    this.$store.dispatch('user/getUserData')
  }
}
</script>

Child component

<template>
  <div>
    <input type="text" value="first" @input="username" />
    <input type="text" value="second" @input="name" />
    <input type="text" value="fifteenth" @input="surname" />
  </div>
</template>

<script>
export default {
  props: {
    first: {
      type: String,
      required: true
    },
    first: {
      type: String,
      required: true
    },
    ...
    fifteenth: {
      type: String,
      required: true
    }
  },
  methods: {
    username() {
     this.$emit('changeUsername', first)
    },
    name() {
     this.$emit('changeName', second)
    },
    surname() {
     this.$emit('changeSurname', fifteenth)
    }
  }
}
</script>

In this variant, I am worried about the number of props. I do not know if this can somehow affect the speed and quality of the component.

  1. Using $ref

Parent component

<template>
  <Child 
    ref='childComponent'
  />
  <button click="submit">Submit</button>
</template>

<script>
import Child from '@/components/Child.vue'

export default {
  data: () => ({
    first: '',
    second: '',
    ...
    fifteenth: ''
  }),
  components: {
    Child
  },
  method: {
    submit() {
      this.$refs.childComponent.submit()
    }
  }
}
</script>

Child component

<template>
  <div>
    <input type="text" v-model="first" @input="username" />
    <input type="text" v-model="second" @input="name" />
    <input type="text" v-model="fifteenth" @input="surname" />
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  data: () => ({
    first: '',
    second: '',
    ...
    fifteenth: ''
  }),
  computed: {
    ...mapGetters({
      user: 'user/getUser'
    })
  },
  methods: {
    submit() {
     //Sending data
    }
  },
  mounted: {
    this.$store.dispatch('user/getUserData')
  }
}
</script>

In this variant, there is not much interaction between the parent and child component. And the parent component fires only the submit method. Plus, you don't need to change the existing code too much, as you just need to move the button. But here I am concerned: is it not a bad decision to apply ref in this way ? And the second problem is the availability of data when the button is clicked. It is more likely that the child component has not yet received the necessary data or simply has not loaded yet, and the user has already pressed the button. For example, the parent component already has a main title and a submit button. But the child component is still loading. Although the solution here can be to make the button available for pressing only after loading the child component using :disabled="isDisabled".

  1. Using vuex

This option immediately seems wrong to me, since the data will be used only inside this component. And vuex seems to me suitable only for global cases.

I have tried all three options and they all work. But I wanted the code not only to work, but also to be correct.

CodePudding user response:

$ref and $emit don't serve the same purpose, there's generally no choice between them.

Option 1 can use a single data prop in a child:

<input
  type="text"
  :value="formData.first"
  @input="$emit('updateFormData', { key: 'first', value: $event.target.value }))"
/>

And merge the changes in a parent:

  <Child 
    :formData="formData"
    @updateFormData="formData[$event.key] = $event.value"
  />

Option 2 is possible in its current state. Alternatively, a child can be made self-contained and contain submit button, then there is no use for $ref. It can emit submit event in case there's business logic associated with form submit that shouldn't be moved to a child.

Option 3 is possible but the use of global store for local purposes is not justified, local stores can be achieved with Vuex dynamic modules.

  • Related