vuejs / router

🚦 The official router for Vue.js
https://router.vuejs.org/
MIT License
3.94k stars 1.19k forks source link

`vm` arg in `next` callback in `beforeRouteEnter` isn't typed to the component #701

Open rudnik275 opened 3 years ago

rudnik275 commented 3 years ago

Version

4.0.2

Reproduction link

https://codesandbox.io/s/competent-architecture-4ezib?file=/src/App.vue

Steps to reproduce

import {defineComponent, ref} from 'vue'

export default defineComponent({
beforeRouteEnter(to, from, next) {
  next(vm => {
    vm.customVariable = 'changed text'
       ^^^  TS2339: Property 'customVariable' does not exist on type 'ComponentPublicInstance{}, {}, {}, {}, {}, {}, {}, {}, false, ComponentOptionsBase >'.
  })
},

setup() {
  const customVariable = ref('')
  return {
    customVariable
  }
}
})

What is expected?

returns correct instance variables

What is actually happening?

Property 'customVariable' does not exist on type 'ComponentPublicInstance{}, {}, {}, {}, {}, {}, {}, {}, false, ComponentOptionsBase >'.

posva commented 3 years ago

As a note: I'm unsure of the feasibility of this one but if someone figures out how to type it, please file a PR!

tinobino commented 3 years ago

For the time being using vue-class-component can help here. For instance with having a class-style Vue component named "Main" you can just do the following:

next(vm => {
   const main = vm as Main;
   main.customVariable = 'changed text';
});

It still feels like a hack but it works at least.

tinobino commented 3 years ago

Please see also my comment here.

notdp commented 3 years ago

I met same problem today,and I solved problem. actually,according to my console log,vm has property customVariable. so this problem same like caused by typescript type.

vm.customVariable = 'changed text' change to: (vm as any).customVariable = 'changed text' It worked for me.

there is my code.

import {defineComponent} from 'vue';
import baseurl from "@/api/baseurl";

const axios = require('axios').default;

export default defineComponent({
  name: 'ProductPage',
  data() {
    const product = {
      id: 0,
      name: '',
      type: '',
      description: '',
      saleUserid: -1,
      salePrice: 0,
      picUrl: ''
    }
    return {
      product,
    };
  },
  props: ['id'],
  beforeRouteEnter(to, from, next) {
    axios.get(baseurl.getProducts + '/' + to.params.id)
        .then((response: any) => {
          next(vm => {
            // console.log(vm)
            // console.log((vm as any).product)
            (vm as any).product = response.data;
          })
          console.log(response);
        })
        .catch(function (error: any) {
          console.log("GET PRODUCT ERROR!!!")
          console.log(error);
        });
  }

});
rw-esc commented 2 years ago

Another option would be to assign the component to a variable and then use vm as InstanceType<typeof Component> e.g.:

import axios from "axios";
import {defineComponent} from "vue";

const Component = defineComponent({

    async beforeRouteEnter(to, from, next) {
        const data = (await axios.get("/products/" + to.params.id)).data;
        next(vm => {
            (vm as InstanceType<typeof Component>).product = data;
        });
    },

    data() {
        return {
            product: {},
        };
    }
});

export default Component;
Akryum commented 1 year ago

You can import the module from itself:

<script lang="ts" setup>
// in MyComp.vue
import type Self from './MyComp.vue'

const foo = 'bar'

defineExpose({
  foo,
})

defineOptions({
  beforeRouteEnter: (to, from, next) => {
    next(async (vm: InstanceType<typeof Self>) => {
      vm.foo // string
    })
  },
})
</script>

@posva NavigationGuard is missing a generic to be able to pass the type of vm in next

Akryum commented 1 year ago

@posva Also the typing seems to be off as the type of the current component is not compatible with the signatures of next: image

posva commented 1 year ago

I don't know where InstanceType comes from. It doesn't come from vue-router but I imagine it should be assignable to ComponentPublicInstance in order for it to work.

NavigationGuard doesn't really need a generic as long as you can specify a more concrete type of component instance. BTW beforeRouteEnter is of type NavigationGuardWithThis, not NavigationGuard

Akryum commented 1 year ago

https://www.typescriptlang.org/docs/handbook/utility-types.html#instancetypetype

Akryum commented 1 year ago

BTW beforeRouteEnter is of type NavigationGuardWithThis

You are sure? I though there is no this in beforeRouteEnter

posva commented 1 year ago

Yes, because it has the this explicitly set to undefined 😅