vuejs / vue-class-component

ES / TypeScript decorator for class-style Vue components.
MIT License
5.8k stars 429 forks source link

Error when referencing props in component class or component methods from watchers #220

Closed tobiasheldring closed 6 years ago

tobiasheldring commented 6 years ago

To Reproduce

Create a new project with vue-cli and select Typescript and Class components as features

update HelloWorld.vue script section to

<script lang="ts">
import Vue from "vue"
import Component from "vue-class-component"

@Component({
  props: {
    msg: String
  },
  watch: {
    msg: function () {
      this.printMessage()
    }
  }
})
export default class HelloWorld extends Vue {
  printMessage() {
    console.log(this.msg)
  }
}
</script>

Compile error

ERROR in /Users/tohe/Projects/test-class-component/src/components/HelloWorld.vue
40:12 Property 'printMessage' does not exist on type 'Vue'.
    38 |   watch: {
    39 |     msg: function () {
  > 40 |       this.printMessage()
       |            ^
    41 |     }
    42 |   }
    43 | })
ERROR in /Users/tohe/Projects/test-class-component/src/components/HelloWorld.vue
46:22 Property 'msg' does not exist on type 'HelloWorld'.
    44 | export default class HelloWorld extends Vue {
    45 |   printMessage() {
  > 46 |     console.log(this.msg)
       |                      ^
    47 |   }
    48 | }
    49 | </script>

Versions

vue-cli 2.9.3 vue-class-component 6.1.2

ktsn commented 6 years ago

Please declare the props type as class properties and annotate your component type on @Component decorator or use vue-property-decorator.

<script lang="ts">
import Vue from "vue"
import Component from "vue-class-component"

@Component<HelloWorld>({
  props: {
    msg: String
  },
  watch: {
    msg: function () {
      this.printMessage()
    }
  }
})
export default class HelloWorld extends Vue {
  msg: string

  printMessage() {
    console.log(this.msg)
  }
}
</script>
KaelWD commented 6 years ago

The readme makes it look like you can use props from the decorator without having to declare them twice: image

Obviously this won't work because of Microsoft/TypeScript#4881, but it is pretty misleading.

ktsn commented 6 years ago

The example in readme is written in JavaScript. If you want to see TS example, please see example directory in this repo.

KaelWD commented 6 years ago

Ah, right. I forgot it was for babel too. Do you think this is a decent way to do it without the double declaration?

const options = Vue.extend({
  props: {
    foo: String,
    bar: Boolean
  }
})

@Component
export default class FooBar extends options {
  ...
}
ktsn commented 6 years ago

Oh, it looks really nice idea 👍 Maybe we should update TypeScript example to use that way.

ru-sh commented 6 years ago

It doesn't work

import Component from "vue-class-component";
import Vue from "vue";

const Props = Vue.extend({
  props: {
    items: {
      type: Array,
      default: () => []
    },
    selectedItems: {
      type: Array,
      default: () => []
    },
    keyFunc: {
      type: Function,
      default: () => {
        return i => i;
      }
    }
  }
});

@Component({
  model: {
    prop: "selectedItems",
    event: "update"
  }
})
export default class CheckboxGroup extends Props {
  selected = this.selectedItems;

  get selectAll(): boolean {
    return this.items ? this.selectedItems.length == this.items.length : false;
  }
  set selectAll(value: boolean) {
    var selected: any[] = [];
    debugger;
    if (value) {
      selected = [...this.items];
    } else {
      selected = [];
    }

    this.update(selected);
  }

  update(items: any[]) {
    this.$emit("update", items);
  }

  itemClick(el: any) {
    let item = el.value;
    let items = (this as any).selectedItems.concat([item]);
    (this as any).update(items);
  }

  isChecked(item: any) {
    let ch = (this as any).selectedItems.filter(i => i === item);
    return ch.length;
  }
}
error  in .\src\components\CheckboxGroup.vue.ts

[tsl] ERROR in .\src\components\CheckboxGroup.vue.ts(47,19)
      TS2339: Property 'selectedItems' does not exist on type 'CheckboxGroup'.

 error  in .\src\components\CheckboxGroup.vue.ts

[tsl] ERROR in .\src\components\CheckboxGroup.vue.ts(50,17)
      TS2339: Property 'items' does not exist on type 'CheckboxGroup'.

 error  in .\src\components\CheckboxGroup.vue.ts

[tsl] ERROR in .\src\components\CheckboxGroup.vue.ts(50,30)
      TS2339: Property 'selectedItems' does not exist on type 'CheckboxGroup'.

 error  in .\src\components\CheckboxGroup.vue.ts

[tsl] ERROR in .\src\components\CheckboxGroup.vue.ts(50,59)
      TS2339: Property 'items' does not exist on type 'CheckboxGroup'.

 error  in .\src\components\CheckboxGroup.vue.ts

[tsl] ERROR in .\src\components\CheckboxGroup.vue.ts(56,27)
      TS2339: Property 'items' does not exist on type 'CheckboxGroup'.
jannikkeye commented 6 years ago

This does indeed not work, how did this make it into the docs? Am I missing something here.

LukasBombach commented 6 years ago

yep, does not work. you're either stuck with:

@Component({
  props: {
    id: Number,
  }
})
export default class SomeComponent extends Vue {
  public id: number;
}

Property 'id' has no initializer and is not definitely assigned in the constructor.

or

@Component({
  props: {
    id: Number,
  }
})
export default class SomeComponent extends Vue {
  public id: number = -1; // initialize variable
}

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "id"

LukasBombach commented 6 years ago

Oh, I just found a fix. Do this:

export default class LightSwitch extends Vue {
  @Prop()
  public id!: number;
}

don't miss the ! after id

plh97 commented 5 years ago

not fix it.

putrasurya commented 5 years ago

Oh, I just found a fix. Do this:

export default class LightSwitch extends Vue {
  @Prop()
  public id!: number;
}

don't miss the ! after id

please explain why

joker7blue commented 5 years ago
export default class LightSwitch extends Vue {
  @Prop()
  public id!: number;
}

It's word very well, thank you so much