73

Template:

<template>
  <div ref="element"></div>
</template>

Script:

export default {
  setup() {
    const element = ref(null);
    return {
      element,
    };
  },
};

This is a normal way to define a ref in vue3, but in JavaScript way. And if I'm using TypeScript, I will need to define a type for value element, right?

How do I do to ensure the correct type for value element?

8 Answers 8

70
<template>
   <ChildComponent ref="childRef">
</template>
<script lang="ts" setup>
import ChildComponent from './ChildComponent.vue'
import { ref } from 'vue'

const childRef = ref<InstanceType<typeof ChildComponent>>()
childRef.value.childMethods()

</script>

Sign up to request clarification or add additional context in comments.

3 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
One addition to this, you need to add defineExpose({ childMethods: childMethods}); to ChildComponent while using setup script, in order to be able to access to the method from the ref in the Parent.
Unfortunately this doesn't work if you're using script setup for ChildComponent. You get this error: TS2344: Type '{}' does not satisfy the constraint 'abstract new (...args: any) => any'.
44

Well, that would depend on whether or not you need it typed and to provide member information (public properties, methods, etc). If you do, then yes you need to define a type for it; otherwise, you don't have to, and can simply access the unwrapped reference with .value which leaves it as type any (I bet you figured out this one).

But if you have to, you need to tell the compiler what it is or what it's going to be assigned on. To do that, you'll want to use the third overload of ref (with no argument) and explicitly set the generic type to the desired type—in your case, you want HTMLDivElement (or simply HTMLElement if you don't care about the specific members it has to offer).

export default defineComponent({
  setup() {
    const el = ref<HTMLDivElement>();

    onMounted(() => {
      el.value // DIV element
    });

    return {
      el
    }
  }
})

In JavaScript, you don't have type checking, so passing null on the ref function is as good as not passing anything (which is especially okay for template refs)*; it could even come across as being misleading in a sense that the unwrapped value actually resolves to something else but null.

* When using the Composition API, the concept of "reactive refs" and "template refs" are unified. And the reason we're accessing this particular type of ref on the mounted hook is because the DOM element will be assigned to it after initial render.

References:

5 Comments

Mind that it is not null, but undefined. So your type of el in this case would be: Ref<HTMLDivElement | undefined> Passing null to a function is not the same as just leaving out that parameter (in which case it is undefinded)
Any idea on how to achieve this when the referenced item is a vue component, not a native DOM element? Is there a way to get the ThisType of the referenced component?
@coyotte508 Does this answer your question?
@YomT. it does!
More actual references (for 2024) is: vuejs.org/guide/typescript/…
17

The line isn't correct:

    const el = ref<HTMLDivElement>();

The el is not an HTMLDivElement. Instead, it's a proxy to the element. The $el of that proxy is the actual HTMLDivElement. Though I'm not sure if I get it right. In a component, I managed to type it as follow:

import { ComponentPublicInstance, defineComponent, ref } from 'vue';

export default defineComponent({
  // snipped...
  setup() {
    const field = ref<ComponentPublicInstance<HTMLInputElement>>();
    return {
      field,
      fun() { field.value.$el.value = 'some value'; }
    };
  }
  // snipped...
});

1 Comment

This is wrong for normal div Elements. For <div ref="test">, use: ref<HTMLDivElement> For <my-app-component ref="test">, use ref<PublicComponentInstance<MyAppComponent>>
10

Vue 3 composition API

<template>
   <input ref="fileInput" style="display: none;" type="file" @change="onFileSelected" />
   <button @click="fileInput?.click()">Pick Files</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const fileInput = ref<HTMLDivElement | null>(null)

function onFileSelected(event: any) { console.log("Event: " + event) }
</script>

Second example

<template>
   <div ref="coolDiv">Some text</div>
   <button @click="changeCoolDiv">Pick Files</button>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const coolDiv = ref<HTMLDivElement>()

function changeCoolDiv() {
  if(coolDiv) {
    coolDiv.value // Div element
  }
}
</script>

Comments

8

From the Vue 3 docs:

Components using <script setup> are closed by default - i.e. the public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside .

To explicitly expose properties in a component, use the defineExpose compiler macro:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

When a parent gets an instance of this component via template refs, the retrieved instance will be of the shape { a: number, b: number } (refs are automatically unwrapped just like on normal instances).

Example code

From the docs:

Component MyModal.vue:

<script setup lang="ts">
import { ref } from 'vue'

const isContentShown = ref(false)
const open = () => (isContentShown.value = true)

defineExpose({
  open
})
</script>

Parent component App.vue:

<script setup lang="ts">
import MyModal from './MyModal.vue'

const modal = ref<InstanceType<typeof MyModal> | null>(null)

const openModal = () => {
  modal.value?.open()
}
</script>

1 Comment

I would like that only showed the methods that are in the "defineExpose"
5
import { VNodeRef } from 'vue';

You can use VNodeRef.

const swiperRef = ref<VNodeRef>();

2 Comments

how would the used type be declared? like const r = ref<VNodeRef<String | null>>('I am only a String or null'); ?
There are two kinds of refs. DOM ref and reactive ref.VNodeRef is good to use with ref targeting dom element while for the normal ref use const name = ref<string | null>(null)
2

If using the vue-class-component package (here the beta v8 but the same with @Component instead of @Option will work in earlier versions):

<template>
  <div ref="element">
    <MyOtherComponent ref="otherComponent" />
  </div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component'
import MyOtherComponent from '../MyOtherComponent.vue'

@Options({
  components: {
    MyOtherComponent,
  },
})

export default class MyComponent extends Vue {
  $refs!: {
    element: HTMLDivElement,
    otherComponent: InstanceType<typeof MyOtherComponent>,
  }

  mounted() {
    this.$refs.otherComponent.myOtherComponentMethod()
  }
}
</script>

1 Comment

How do I do that with the options API?
2

According the official docs: Typing ref(), use type Ref in vue

Example:

import type { Ref } from 'vue'

name: Ref<string> = ref()

interface IUser {
  name: string,
  sex: string
}

user: Ref<IUser> = ref({
  name: '',
  sex: ''
})

2 Comments

I don't think (capital-R) "Ref" is needed? The docs say: "Or, by passing a generic argument when calling...", so why not just use (small-r) ref<IUser>()?
@Kalnode It's OK in most cases, if u use TypeScript it return a class will more support.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.