kaorun343 / vue-property-decorator

Vue.js and Property Decorator
MIT License
5.52k stars 380 forks source link

`this` typing broken? #416

Closed NicoAiko closed 2 years ago

NicoAiko commented 2 years ago

Describe the bug There seems to be a difference in the Vue-Property-Decorator class instance and Vue.extend as far as the this typing is concerned. This is problematic for me as I am using @casl/vue which defines global properties into the Vue prototype. Basically $ability and $can are accessible via this.

This is solely a typing problem. On runtime, both versions work just fine. But when developing, only the Vue.extend approach is error-free.

To Reproduce Steps to reproduce the behavior:

  1. Clone https://github.com/NicoAiko/casl-vue-property-decorator-bug
  2. npm i to install dependencies
  3. Open project in VSCode
  4. Open src/App.vue on one side and src/components/HelloWorld.vue on the other
  5. App.vue should not show any problems, but HelloWorld.vue should show an error in line 76

Error message: Argument of type '["read", "Article"]' is not assignable to parameter of type 'Parameters<this["$ability"]["can"]>'.

Expected behavior Should not show error.

Screenshots Bildschirmfoto 2022-01-28 um 16 37 09 Bildschirmfoto 2022-01-28 um 16 37 16 Bildschirmfoto 2022-01-28 um 16 37 42

What it shouldn't be: Bildschirmfoto 2022-01-28 um 16 39 12

What it should be: Bildschirmfoto 2022-01-28 um 16 39 39

Desktop (please complete the following information):

creage commented 2 years ago

@NicoAiko here is the solution:

To define application specific Ability type, create a separate file, for example:

import { Ability, AbilityClass } from '@casl/ability';

type Actions = 'create' | 'read' | 'update' | 'delete';
type Subjects = 'Article' | 'User'

export type AppAbility = Ability<[Actions, Subjects]>;
export const AppAbility = Ability as AbilityClass<AppAbility>;

By default, Vue['$ability'] is declared as AnyAbility type. So, to make it more useful for our app, we need to redeclare Vue['$ability'] type. To do so, create src/shims-ability.d.ts file:

import { AppAbility } from './AppAbility'

declare module 'vue/types/vue' {
  interface Vue {
    $ability: AppAbility;
    $can(this: this, ...args: Parameters<Vue['$ability']['can']>): boolean; // WE MUST USE Parameters<Vue here!
  }
}
declare module 'vue/types/options' {
  interface ComponentOptions<V extends Vue> {
    ability?: AppAbility;
  }
}

And update tsconfig.json to replace default vue modules augmentation (i.e., @casl/vue/patch) with application specific:

{
  "compilerOptions": {
    // other options
    "baseUrl": ".",
    "paths": {
      // other mappings
      "@casl/vue/patch": [
        "src/shims-ability.d.ts"
      ]
    }
  },
  // other options
}

Enjoy abilities autocompletion.