How to foward $refs in Vue

Viewed 5722

I have a component which should pass everything on to the child. I'm successfully passing $attrs and $listeners already:

<template>
  <el-form v-on="$listeners" v-bind="$attrs" :label-position="labelPosition">
    <slot />
  </el-form>
</template>

But I'm unsure how to also forward $refs like we can do in React, so that when using my component like this:

<el-form-responsive
  class="form"
  :model="formValues"
  status-icon
  :rules="rules"
  ref="form"
  label-width="auto"
  @submit.native.prevent="submitForm"
>

Then this.$refs.form is actually a reference to the child <el-form>.

I would rather do this transparently, as in you pass exactly the same props to el-form-responsive as you would to a el-form without needing to know that refs has to be passed in a special way.

4 Answers

I don't think it is possible to directly mimic React's ref. A ref attribute in Vue is just a string which is used to register a child component reference to the parent's $refs object during the render function.

Here are the links to documentations doc & doc

So basically it's a kind of inverted logic.. instead of passing a ref to a child in Vue we get it from the child into the parent. So it's not really possible at this point to create a grandchild reference, which is what you need.

There are some workarounds though.

1. Quick dirty and not transparent but technically it would work: In the parent component, which uses your el-form-responsive, on mounted hook we could replace the original child reference with the grandchild ref.

Your el-form-responsive component. Template:

<el-form ref="elform">

A parent which uses your el-form-responsive. Template:

<el-form-responsive ref="form">

Script:

...
mounted () {
  this.$refs.form = this.$refs.form.$refs.elform
}

And after this this.$refs.form is actually a reference to the granchild <el-form>

2. This one would be more elaborate, but probably mach better then the first method:

In order to make the el-form-responsive component really transparent you could expose some of the methods and properties from the child el-form component to any potential parent. Something like this:

el-form-responsive. Template:

<el-form ref="elform">

Script:

export default {
  data: () => ({
    whatever: null
  }),
  mounted () {
    this.whatever = this.$refs.elform.whatever
  },
  methods: {
    submit () {
      this.$refs.elform.submit()
    }
  }
}

So then inside some parent el-form-responsive could be used like this:

<el-form-responsive ref="form">
...
mounted () {
  const formWhatever = this.$refs.form.whatever // actually `whatever` from `el-form`
  this.$refs.form.submit() // eventually calls submit on `el-form`  
},

If you are working with a custom component with the script setup in Vue 3, it's worth nothing that template refs work like this.

Essentially, you will have to use defineExpose to "expose" your child component data to the parent component

Try this to replace parent's ref with child's , In el-form-responsive

<template>
  <el-form v-on="$listeners" v-bind="$attrs" :label-position="labelPosition" ref='ref'>
    <slot />
  </el-form>
</template>

mounted () {
  Object.entries(this.$parent.$refs).forEach(([key, value]) => {
    if (value === this) {
      this.$parent.$refs[key] = this.$refs.ref
    }
  })
...

It is work for me:

mounted() {
   Object.assign(Object.getPrototypeOf(this), this.$refs.formRef)
}

warning: insert to __proto__,maybe is bad way

Related