Props typing in Vue.js 3 with TypeScript
Asked Answered
A

7

65

I'm trying to type hint my props in a Vue 3 component, with composition API.

So, I'm doing this:

<script lang="ts">
import FlashInterface from '@/interfaces/FlashInterface';
import { ref } from 'vue';
import { useStore } from 'vuex';

export default {
    props: {
        message: {
            type: FlashInterface,
            required: true
        }
    },
    setup(props): Record<string, unknown> {
        // Stuff
    }
};

My FlashInterface looks like this:

export default interface FlashInterface {
    level: string,
    message: string,
    id?: string
}

This interface works well except in this case where I got this error:

ERROR in src/components/Flash.vue:20:10
TS2693: 'FlashInterface' only refers to a type, but is being used as a value here.
    18 |    props: {
    19 |        message: {
  > 20 |            type: FlashInterface,
       |                  ^^^^^^^^^^^^^^
    21 |            required: true
    22 |        }
    23 |    },

I don't understand why TypeScript thinks this is a value.

What am I missing?

Arietta answered 14/11, 2020 at 7:21 Comment(0)
U
118

You should use it with PropType imported from vue like Object as PropType<FlashInterface>:

import FlashInterface from '@/interfaces/FlashInterface';
import { ref,PropType, defineComponent } from 'vue';
import { useStore } from 'vuex';

export default defineComponent({
    props: {
        message: {
            type: Object as PropType<FlashInterface>,
            required: true
        }
    },
    setup(props) {
            // Stuff
        }
});

Note : you should create your component using defineComponent in order to get the types inference.

script setup

<Vue3.3 :

You could define the props using defineProps function in two ways, but you should declare the types/interfaces inside the script setup as mentioned here :

<script setup lang="ts">

import { ref,PropType, defineComponent , defineProps} from 'vue';
interface FlashInterface {
    level: string,
    message: string,
    id?: string
}
interface IProps{
   message : FlashInterface
}
const props = defineProps<IProps>()

or

<script setup lang="ts">
...
interface FlashInterface {
    level: string,
    message: string,
    id?: string
}
const props = defineProps({
        message: {
            type: Object as PropType<FlashInterface>,
            required: true
        }
    })

Update Vue 3.3+

Set in your package.json under dependencies

"vue": "^3.3.0"

Now the defineProps<T>() macro accept an imported interface and you can do this:

import FlashInterface from '@/interfaces/FlashInterface';
import { defineProps } from 'vue';

export default defineProps<FlashInterface>();

If you need to specify default values, you can do as follows:

const { 
  message = "No message :(",
  level,
  id,
} = defineProps<FlashInterface>();

From the PR 8083 officially added in Vue 3.3.0

Umbilical answered 14/11, 2020 at 8:59 Comment(3)
What about if props type is array? It will be Array as PropType<FlashInterface> or Object as PropType<FleshInterface[]> ??Layton
@FarhodNematov it should be type: Array as PropType<FlashInterface[]>, or type: Array as PropType<Array<FlashInterface>>,Umbilical
According to the official documentation, using interfaces imported from another file won't make type-checking work. vuejs.org/guide/typescript/…Stegman
G
5

Two liner

<script setup lang="ts">
import FlashInterface from '@/interfaces/FlashInterface';
defineProps<{flash: FlashInterface;}>();
</script>
Glace answered 16/7, 2022 at 5:1 Comment(2)
Please add more details about your answerUmbilical
This CANNOT work. You can't use imported type in defineProps as of today. See github.com/vuejs/core/issues/4294#issuecomment-1316097560Ajit
I
4

The compiler is complaining about missing a reference for a (custom) constructor during type checking (links to legacy docs but works the same way with the most recent version of Vue).

In Typescript, you might want to think of interfaces as a contract that an entity should conform to, so they aren't really a constructor, and therefore, we need to provide an implementation of those interfaces.

Since you are on Typescript, if you need to keep the interface, consider using the class equivalent:

// Name the constructor whatever you like,
// but I would personally prefix interfaces with an "I"
// to distinguish them with the constructors

class Flash implements FlashInterface {
  level: string;
  message: string;
  id?: string

  constructor() {
    // Be sure to initialize the values for non-nullable props
    this.level = '';
    this.message = '';
  }
}

export default {
  name: 'Home',

  props: {
    message: Flash
  }
}

An excerpt from the doc:

In addition, type can also be a custom constructor function and the assertion will be made with an instanceof check. For example, given the following constructor function exists:

props: {
  message: {
    type: function Person(firstName, lastName) {
      this.firstName = firstName
      this.lastName = lastName
    }
  }
}

And of course, another alternative would be as suggested in the other post with PropType. Either one will do. It's just a matter of preference, I guess.

Intermixture answered 14/11, 2020 at 12:14 Comment(0)
H
4

You can use setup script to define your props

<script setup lang="ts">
import { ref } from 'vue';
import { useStore } from 'vuex';
interface FlashInterface {
    level: string,
    message: string,
    id?: string
}
interface Props{
   message:FlashInterface
}
const props = defineProps<Props>()

for more information: Vue doc

Hirokohiroshi answered 12/3, 2022 at 11:56 Comment(2)
This CANNOT work. You can't use imported type in defineProps as of today. See github.com/vuejs/core/issues/4294#issuecomment-1316097560Ajit
Yes, You're right. I've seen that in vue document and forgot to change my answer. thanks to point that tipHirokohiroshi
T
2

We should use PropType from 'vue' for Interface that developer created manually and define default value!

import FlashInterface from '@/interfaces/FlashInterface';
import { ref,PropType, defineComponent } from 'vue';
import { useStore } from 'vuex';

export default defineComponent({
   props: {
       message: {
           type: Object as PropType<FlashInterface>,
           default: {}
       }
   },
// stuff
});
Tribesman answered 21/7, 2022 at 19:44 Comment(0)
B
1

I have an idea to get around this by introducing a generic props type and a function to enforce correct implementation of the props.

type TypedProps<T> = {
  [key in keyof T]: {
     type: PropType<T[key]>
     required?: boolean
  } | PropType<T[key]>
}

function typedProps<T extends object>(props: TypedProps<T>): TypedProps<T> {
  return props
}

I would use it in defineComponents like this

interface MyProps {
  prop1: string
  prop2: CustomInterface
}
export default defineComponent({
  props: typedProps<MyProps>({
    prop1: String,
    prop2: Object as PropType<CustomInterface>
  })
})

Though, it does not work as intended with the required key. Maybe someone could explain why or propose a correction.

Brawn answered 6/6, 2022 at 10:31 Comment(0)
D
1

Object as PropType strikes me as funky and nearly un-rememberable syntax. A much nicer way of typing properties is to put a generic type argument inline in the defineProps call, so, with <script setup lang="ts">...

const props = defineProps<{ message: FlashInterface }>()
Divulgate answered 14/6, 2022 at 22:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.