brainbag
brainbag

Reputation: 1057

How do I add a property to an array type?

In Vue, we have this type:

Vue.$refs: {
    [key: string]: Vue | Element | Vue[] | Element[];
}

I have a third-party library (vuelidate) that adds a $v property to everything Vue. It does not define types, so I'm doing it myself.

declare module 'vue/types/vue' {
  interface Vue {
    $v: any
  }

  interface Element {
    $v: any
  }
}

When I try to use $v on $refs like so, typescript complains:

this.$refs.form.$v

Results in

Property '$v' does not exist on type 'Vue[]'.

That makes sense to me. What doesn't make sense, though, is how to add a property to Vue[] and Element[] so that this code compiles.


I've poured over the documentation, Stack Overflow, you name it, and I can't find a solution to this. How do I add a type to a specific array?

I understand why this doesn't work:

interface Vue[] {
  $v: any
}

But I have no idea what to do to fix it.

Upvotes: 0

Views: 128

Answers (1)

Matt McCutchen
Matt McCutchen

Reputation: 30879

Casting works, but I appreciate your desire to declare types so you don't have to cast every time. Vue[] is actually shorthand for Array<Vue>, a parameterization of the built-in generic interface Array. You can add properties to Array, but then they will be declared on arrays of all types, not just Vue[]. You can mitigate this a bit with a conditional type, like so:

// Wrap with `declare global { ... }` if the file is an ES6 module
interface Array<T> {
    $v: T extends Vue | Element ? any : unknown;
}

But truly, you don't want $v on every Vue[], but only on Vue.$refs. Unfortunately, given the way Vue.$refs is declared, I don't believe there's any way to make this change via augmentation. You'd have to add a modified version of the vue package to your project and change the declaration to read:

export interface Vue {
  // ...
  readonly $refs: { [key: string]: (Vue | Element | Vue[] | Element[]) & {$v: any} };
  // ...
}

Obviously, this change would not be appropriate to send upstream. But you could send a PR to add a hook that other packages such as vuelidate would then be able to augment:

// In vue/types/vue.d.ts:
export interface RefExtra {}
export interface Vue {
  // ...
  readonly $refs: { [key: string]: (Vue | Element | Vue[] | Element[]) & RefExtra };
  // ...
}

// In vuelidate typings (or your project for now):
declare module 'vue/types/vue' {
  interface RefExtra {
    $v: any
  }
}

Upvotes: 1

Related Questions