Mastering Vue3: Typed Component Instance Template Refs with Exposed Methods

In my project, I am working with a component called A that has a method called send.

Here is an example of how Component A is structured in A.vue:

<script setup lang="ts">
function send(data: string) {
  console.log(data)
}

defineExpose({ send })
</script>

Now, I have another component called B which imports Component A.

This is how B.vue looks:

<template>
  <ComponentA ref="a" />
</template>
<script setup lang="ts">
import ComponentA from '@/component-a.vue'

const a = ref()

onMounted(() => a.value.send(22)) // there should be a type error here
</script>

My question is, how can I properly type the imported component so that when calling its methods through refs, it validates the method names and argument types?

I tried using

ComponentPublicInstance<typeof ComponentA>
as found through research, but it doesn't seem to perform the desired method checks.

UPDATE:

Below is the content of shims-vue.d.ts (generated by vue-cli):

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

UPDATE:

For a full reproduction scenario, please check out the repository.

As shown in the HelloWorld.vue file, the variable a is a ref to an instance of ComponentA. I attempted to type it using

const a = ref<InstanceType <typeof ComponentA>>()
, but unfortunately, it still gets typed as any.

https://i.sstatic.net/fpKMc.png

Answer №1

When using the ref() function, the resulting instance type is a Ref<T>. The generic type T, by default set to any, determines the type of its .value property.

If you do not specify the generic type and there is no initializer argument to infer it from, TypeScript will assume that T is any. Therefore, a.value ends up being of type any, leading to a.value.send also being of type any, lacking in type safety, as you may have noticed.

To address this issue, you should define the generic type for ref like so:

                       👇
const a = ref<InstanceType<typeof ComponentA>>()
a.value?.send(22) // error: Argument of type 'number' is not assignable to parameter of type 'string'.

https://i.sstatic.net/7ngIJ.png

Answer №2

Regarding TypeScript, ComponentA is classified as type

DefineComponent<{}, {}, any>
, due to the declaration in your shims-vue.d.ts file. It is advisable to import ComponentA instead of destructuring it with { ComponentA }. Since TypeScript lacks the ability to interpret Vue SFC files, it relies on the information provided in your .d.ts file.

In IDEs such as VSCode with Volar or Vetur extensions, you can expect more precise type validation compared to standalone TypeScript. This is because these tools are capable of understanding Vue SFC files and providing accurate typings, bypassing the need for global shims.

If you seek enhanced TypeScript checking without IDE assistance, the Vue Typescript Overview recommends utilizing vue-tsc. This tool acts as a TypeScript wrapper that comprehends SFC files similar to Volar, enabling better validation of your components. Additionally, using vue-tsc can generate a customized .d.ts file specific to your project, furnishing TypeScript with the necessary context for validating SFC components independently from Vue-aware tools.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilize the v-if directive with nested object properties for dynamic conditional rendering

I need to verify if the item object has a property called 'url' set. If it's not present, I would like to display a placeholder image. Here is an example of what I want to achieve: <img v-if="item.relationships.main ...

Achieving CommonJS imports compilation with Typescript

In my TS file, I've included a 3rd party package using import XXX { YYY, ABC, 123 } from 'XXX'; While it compiles to CommonJS without any issues, I'd prefer to have it compiled to an ESModule instead. I tried changing the target and mo ...

Do parallel awaits in JS/TS work only on Chrome browsers exclusively?

Encountering a strange issue with promise resolution behavior in JS/TS. Using Node LTS. It seems that the difference lies in whether the promise resolves to a value that is later read in the code or if it's simply fire-and-forget (void response type). ...

How can Vue.js transfer form data values (using v-model) from a Parent component to a Child component?

I am working on a multistep form using vue.js. The parent form collects two inputs and once these are validated, it moves to the next step which involves a child component. I want to pass the values from the parent component to the child component's f ...

Issue encountered with Typescript and Mongoose while operating within a Kubernetes cluster environment with Skaffold configuration

Here is the code snippet, const userSchema = new mongoose.Schema({ email: { type: String, required: true, }, password: { type: String, required: true, }, }); console.log(userSchema); userSchema.statics.build = (user: UserAttrs) =& ...

Trouble arises when trying to import Jest with Typescript due to SyntaxError: Import statement cannot be used outside a module

Whenever I execute Jest tests using Typescript, I encounter a SyntaxError while importing an external TS file from a node_modules library: SyntaxError: Cannot use import statement outside a module I'm positive that there is a configuration missing, b ...

Retrieving the value of the selected item when it is changed

I am currently using v-select/v-autocomplete in my project: <v-autocomplete v-modal="newRole" :items="roles" label="--Change role--" required return-object item-value="id" item-text=&qu ...

The error at core.js:4002 is a NullInjectorError with a StaticInjectorError in AppModule when trying to inject FilterService into Table

While exploring PrimeNg Table control in my application - as a beginner in PrimeNg & Angular, I encountered an error No provider for FilterService! shown below: core.js:4002 ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppMo ...

Utilize axios as the API wrapper in a global context within a Vue project

I currently have around 13 Axios requests in my Vue application that are very similar. axios({ method: 'post', url: `${this.$root.api_url}/v2/cameras/${this.selected.exid}/nvr/snapshots/extract`, data: { ...

How can I add a parameter to a JSON URL in Angular?

I'm looking to enhance my URL by adding a new parameter, but I'm unsure of the steps needed. ts.file route(name:string) { this.router.navigate(['/homepage', (name)]); console.log('name); } service private url1 = './assets/ ...

Definitions for Typescript types that describe a custom hook responsible for fetching a specific part of the Redux state

I've created a custom hook called useReduxState to fetch a specific piece of state from Redux like so: const STATE_A = useReduxState("STATE_A"); Now, I'm running into issues when trying to integrate Typescript. These are the types I a ...

Developing a constrained variable limited to specific values

Recently delving into Typescript - I am interested in creating a variable type that is restricted to specific values. For instance, I have an element where I want to adjust the width based on a "zoom" variable. I would like to define a variable type call ...

Having difficulties utilizing React Syntax Highlighter in Next.js 13 with Sanity version 3

Hello there, I've encountered a problem with my project while using Sanity v3 and React Syntax Highlighter. Initially, I had success displaying my code in the browser by utilizing the Refactor library after following a tutorial on the Code Input by Sa ...

Working with an array of object in Vuex for form handling

Looking to make updates to a vuex store that includes an array of objects: Users have a combobox for making selections, which then updates a property of the object in the database. However, every time I choose an item from the autocomplete (combobox) I e ...

One versatile method to handle multiple v-models in Vue.js

Is it possible to implement input validation for multiple fields using a common function in Vue script? <input type="number" v-model="field1" @keypress="validateInput" > <input type="number" v-model="field2"@keypress="validateInput" ><input ...

What is the process for deploying a Lambda function using Terraform that has been generated with CDKTF

Currently, I am following a tutorial by hashicorp found at this link. The guide suggests using s3 for lambda deployment packages. // in the process of creating Lambda executable const asset = new TerraformAsset(this, "lambda-asset", { ...

Looking for guidance on creating a TypeScript interface within React?

How can I properly declare the tagItems in the following code? I am currently getting a warning in VSCode that reads: (property) tagItems: [{ id: number; label: String; name: String; state: Boolean; }] Type '[{ id: number; label: stri ...

Error: Code cannot be executed because the variable "sel" has not been defined in the HTML element

Every time I try to click on the div, I encounter an error message stating 'Uncaught ReferenceError: sel is not defined at HTMLDivElement.onclick' I am currently developing with Angular 8 and this error keeps popping up. I have read through simil ...

Having trouble with CSRF validation while using Django Rest and Vue.js with Axios?

When trying to integrate Django Rest Framework with VueJS and Axios, I consistently encounter the following error message: "CSRF Failed: CSRF token missing." Despite my frontend header appearing to be correct and the cookie loading correctly into the head ...

Tips for remaining synced after submitting a form connected to a Google Form

On my website, I have a contact form that is integrated with a Google form. The action URL of the Google form has been added as the execute URL so that when the form is submitted, it populates the Google form with the data. This is how I implemented it. H ...