Skip to content
This repository has been archived by the owner on Feb 7, 2024. It is now read-only.

Method does't exist in $ref component #257

Open
cawa-93 opened this issue Sep 4, 2019 · 4 comments
Open

Method does't exist in $ref component #257

cawa-93 opened this issue Sep 4, 2019 · 4 comments

Comments

@cawa-93
Copy link

cawa-93 commented Sep 4, 2019

Example:

// Child component
@Component

export default class CommentForm extends Vue {
  public focus() {
    return 1;
  }
}
// Parent component
import CommentForm from '@/components/comment-form.vue';

@Component({
  components: {CommentForm},
})

export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm;

   public tryFocus() {
        this.commentField.focus(); // <- Error: TS2339: Property 'focus' does not exist on type 'Vue'.
    }
}
@kaorun343
Copy link
Owner

@cawa-93
Hi.

This issue is caused by the type definition of *.vue file, and not caused by vue-property-decorator.
*.vue files are defined to export Vue.

I recommend you to ask this question at Vue's official forum or its discord server.

Here is my solution. I usually separate component definition into .vue and .ts.

<script>
// CommentForm.vue
import CommentForm from './CommentForm'

export default CommentForm
</script>
// CommentForm.ts
@Component
export default class CommentForm extends Vue {
  public focus() {
    return 1;
  }
}
<script>
// Comments.vue
import Comments from './Comments'
import CommentForm from  '@/components/comment-form.vue'

export default Comments.extend({
  components: { CommentForm }
})
</script>
// Comments.ts
import CommentForm from '@/components/comment-form';

@Component
export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm;

   public tryFocus() {
        this.commentField.focus(); // no error
    }
}

@duola8789
Copy link

I also encountered this problem, do you have any solutions except separating component definition into .vue and .ts?

@jyork03
Copy link

jyork03 commented Jan 31, 2020

I'm also experiencing this issue and would like to find a solution that doesn't require separating the component definition into multiple files.

Given the example from @cawa-93 :

export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm;

   public tryFocus() {
        this.commentField.focus(); // <- Error: TS2339: Property 'focus' does not exist on type 'Vue'.
    }
}

I would expect this.commentField to be recognized as type CommentForm, which extends Vue and has the method focus() defined, and not as it's ancestor type: Vue.

EDIT
Excuse my ignorance, I'm just starting to learn typescript...
Alright, I believe this behavior is a result of the declaration file shims-vue.d.ts. It causes all *.vue files to be exported as Vue types. @kaorun343 stated as much, but I (and possibly others) didn't quite understand exactly what he meant. The contents of the file are:

declare module '*.vue' {
  import Vue from 'vue';

  export default Vue;
}

Anyway, I've found a couple ways around it.

  1. Using // @ts-ignore. In my editor (Webstorm), this preserves Go To Definition functionality
@Component
export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm;

   public tryFocus() {
        // @ts-ignore
        this.commentField.focus(); // no error
    }
}
  1. Import it as any type. This breaks Go To Definition functionality
@Component
export default class Comments extends Vue {
  @Ref() readonly commentField!: any;

   public tryFocus() {
        this.commentField.focus(); // no error
    }
}
  1. Create an interface for the component class and set the Ref to the interface type. This might be the typescript-purist way to handle this, but it also adds a lot of overhead.
// components/types/index.ts
export interface CommentFormInterface {
  focus: () => number;
}
// Child component
@Component
export default class CommentForm extends Vue implements CommentFormInterface {
  public focus() {
    return 1;
  }
}
import CommentForm from '@/components/comment-form.vue';
import { CommentFormInterface } from '@/components/types';

@Component({
  components: { CommentForm },
})
export default class Comments extends Vue {
  // @Ref() readonly commentField!: CommentFormInterface; // Go To Definition navigates to interface definition
  // or 
  @Ref() readonly commentField!: CommentForm & CommentFormInterface; // Go To Definition navigates to CommentForm component definition.

   public tryFocus() {
        this.commentField.focus(); // no error
    }
}
  1. Merge the type. This might be repetitive, but it carries less boilerplate than option 3. It also breaks Go To Definition
@Component
export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm & { focus: () => number };

   public tryFocus() {
        this.commentField.focus(); // no error
    }
}

As for me, I'll probably just go with option 1 and silence the issue with // @ts-ignore since this is an issue caused by the declaration in shims-vue.d.ts. Until something better comes along, and Vue components can export itself intact, everything will be a work-around anyways.

@macfilho2
Copy link

macfilho2 commented Feb 25, 2022

I did handle this issue as following:
Create a type with a property $el as a HTMLElement

export type VueComponent = { $el: HTMLElement }

And use solution 4 from @jyork03:

@Component
export default class Comments extends Vue {
  @Ref() readonly commentField!: CommentForm & VueComponent

   public tryFocus() {
        this.commentField.focus(); // no error
    }
}

Thus you don't need to write all the HTMLElement methods and properties

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants