Vue: Vuejs type inference does not work with TS 3.4.x

Created on 15 Apr 2019  路  23Comments  路  Source: vuejs/vue

Version

2.6.10

Reproduction link

https://github.com/Patcher56/ts3.4.x-vuejs-issue

Steps to reproduce

  1. create a new vue typescript project (without class syntax) vue create test-ts
  2. open src/components/HelloWorld.vue
  3. add a data variable and try to access it in a computed property

What is expected?

No errors (as the data is defined)

What is actually happening?

Typescript error 2339 telling me
Property 'test' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{ msg: string; }>>'.

image


I think this has something to do with the changed type inference in Typescript 3.4.x.

Important: Change vetur settings to use workspace dependencies to show errors directly in vscode, else you will only get the error when using yarn serve.

typescript

Most helpful comment

One workaround is assigning a return type to computed

type TypedDef<Data, Computed> =
  ComponentOptions<Data, Computed> &
  ThisType<Data & Computed>

type DataDef<Data> = () => Data

export interface ComponentOptions<Data, Computed> {
  data?: DataDef<Data>
  computed?: Accessors<Computed>
}
export type Accessors<T> = {
  [K in keyof T]: () => T[K]
}

declare function component<Data, Computed>(def: TypedDef<Data, Computed>): void;

component({
  data() {
    return {
      foo: 23
    }
  },
  computed: {
    bar(): number { // adding the return type, seems to solve the error
      return this.foo + 1
    }
  }
})

All 23 comments

Seems to belong into the vetur repository then, right?

No, because the errors also appear in the terminal when using yarn serve.
Vetur is just responsible for showing me the errors directly in vscode.

patrick@patrick-Ubuntu:~/dev/test/test$ yarn serve
yarn run v1.15.2
$ vue-cli-service serve
 INFO  Starting development server...
Starting type checking service...
Using 1 worker with 2048MB memory limit
 98% after emitting CopyPlugin                                                 

 DONE  Compiled successfully in 2925ms                                                                                                                                        09:12:39

Type checking in progress...

  App running at:
  - Local:   http://localhost:8081/ 
  - Network: http://10.25.1.148:8081/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

ERROR in /home/patrick/dev/test/test/src/components/HelloWorld.vue
104:19 Property 'test' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{ msg: string; }>>'.
    102 |   computed: {
    103 |     testComp: function() {
  > 104 |       return this.test === false;
        |                   ^
    105 |     }
    106 |   }
    107 | });
Version: typescript 3.4.3
Time: 3161ms

/cc @HerringtonDarkholme @ktsn

Looks like TS3.4 has multiple inference issue...

The minimal repro:

Vue.component('test', {
  data() {
    return {
      test: 123
    }
  },
  computed: {
    ttt() {
      return this.test + 1
    }
  },
})

Also spotted another failure:

The catch-all type signature of component

  component(id: string, definition?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;

is causing union type to fail checking.

I need more time for investigation...

I wonder if properties in data are supposed to become properties of the instance when using TypeScript?

Of course...

I filed the same bug against Typescript (Microsoft/TypeScript#30854)

Minimal repro:

type TypedDef<Data, Computed> =
  ComponentOptions<Data, Computed> &
  ThisType<Data & Computed>

type DataDef<Data> = () => Data

export interface ComponentOptions<Data, Computed> {
  data?: DataDef<Data>
  computed?: Accessors<Computed>
}
export type Accessors<T> = {
  [K in keyof T]: () => T[K]
}

declare function component<Data, Computed>(def: TypedDef<Data, Computed>): void;

component({
  data() {
    return {
      foo: 23
    }
  },
  computed: {
    bar() {
      // return this.foo + 1 // comment out the return solves the problem
    }
  }
})

The problem seems to be cyclic inference issue. If the computed property doesn't reference data, the compiler error goes away.

I think it should goes to TypeScript's repo.

One workaround is assigning a return type to computed

type TypedDef<Data, Computed> =
  ComponentOptions<Data, Computed> &
  ThisType<Data & Computed>

type DataDef<Data> = () => Data

export interface ComponentOptions<Data, Computed> {
  data?: DataDef<Data>
  computed?: Accessors<Computed>
}
export type Accessors<T> = {
  [K in keyof T]: () => T[K]
}

declare function component<Data, Computed>(def: TypedDef<Data, Computed>): void;

component({
  data() {
    return {
      foo: 23
    }
  },
  computed: {
    bar(): number { // adding the return type, seems to solve the error
      return this.foo + 1
    }
  }
})

One workaround is assigning a return type to computed

Good call. Just discovered that workaround is documented here.

The best way to avoid the issue now is to annotate return type of computed method... We cannot make much progress since it is an upstream issue in TS.

The best way to avoid the issue now is to annotate return type of computed method... We cannot make much progress since it is an upstream issue in TS.

Unfortunately it also happens if you have a validator on a prop, and it isn't fixed with the annotation workaround:

export default Vue.extend({
  props: {
    foobar: {
      required: true,
      type: String,
      validator(foobar: string): boolean { return ['foo', 'bar'].includes(foobar); },
    },
  },

  data(): { initialFoobar: string } {
    return {
      initialFoobar: this.foobar, //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    };
  },

  computed: {
    foobang(): string {
      return `${this.foobar}!`; //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    },
  },
});

Breaks our entire build, easily 100+ errors after stepping up from TS v3.3.3333 to v3.4.4. Too bad, too, because this version of TS introduced some handy features for _exactly_ what I'm working on right now... 馃槱

Also happens if you have a Function-type prop:

export default Vue.extend({
  props: {
    foobar: {
      required: true,
      type: String,
    },

    // With this, errors. Without this, no errors.
    someFunc: {
      required: true,
      type: Function,
    },
  },

  data(): { initialFoobar: string } {
    return {
      initialFoobar: this.foobar, //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    };
  },

  computed: {
    foobang(): string {
      return `${this.foobar}!`; //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    },
  },
});

(Note: the two examples I gave are not fixed by the workaround mentioned)

/cc @DanielRosenwasser

@kjleitz not sure what's the issue, it seems to work on my vscode:
image

I would recommend annotation the function with PropType

export default Vue.extend({
  props: {
    foobar: {
      required: true,
      type: String,
    },

    someFunc: {
      required: true,
      type: Function as PropType<()=>any>, // try annotate with a type
    },
  },

  data(): { initialFoobar: string } {
    return {
      initialFoobar: this.foobar, //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    };
  },

  computed: {
    foobang(): string {
      return `${this.foobar}!`; //=> Property 'foobar' does not exist on type 'CombinedVueInstance<Vue, {}, {}, {}, Readonly<{}>>'
    },
  },
});

What typescript and vuejs versions are you using?

Please refer to the comment here:
https://github.com/Microsoft/TypeScript/issues/30854#issuecomment-485893681

did this

//`/types/myVue.d.ts`
import Vue from "vue";
declare module "vue/types/vue" {
  interface Vue {
    $myConfig: string;
  }
}

got this

/src/views/bussiness/bussiness.vue

鍥剧墖

/src/main.ts

鍥剧墖

Is it because I did something wrong?

Type inference errors and incorrect mounting of declarations are very tough issues.

We also had a component start throwing a lot of errors after upgrading from TS 3.3.3333 to 3.4.5

Thanks to this comment, went back and checked that component and, sure enough, one of the computed properties did not have a return type annotated. With that annotation added, the build compiled without issue.

Per the TS comment, perhaps the docs would be best updated as
"you will need to annotate the return type on methods like render and those in computed"
(though the docs are already pretty clear on the cases that are now breaking)

Yeah, unfortunately it sounds like this was just the same limitation we've always known. My best advice is to make an ESLint rule that catches this, since it is hard to report circularities for us during inference.

To add to @kjleitz unresolved issues, a prop type of Vue also breaks it.

I found a very similar issue, with the exact same error, if I used a render() method that used some "props" and wasn't annotated as returning VNode.

Weirdly, instead of getting errors about the render method, or about "props", I got errors about some "data" I used in the mounted() and beforeDestroy() methods.

Best workaround is to use the new Vue Composition API. It is a lot cleaner for typescript and works also with typescript versions > 3.3.4000.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

franciscolourenco picture franciscolourenco  路  3Comments

aviggngyv picture aviggngyv  路  3Comments

hiendv picture hiendv  路  3Comments

paulpflug picture paulpflug  路  3Comments

6pm picture 6pm  路  3Comments