vuejs / router

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

watchEffect, computed and rerender triggered even if param is not changing #835

Closed mathieutu closed 3 years ago

mathieutu commented 3 years ago

Version

4.0.5

Reproduction link

https://codesandbox.io/s/vue-router-params-issue-qd5vt?file=/src/views/About.vue

Steps to reproduce

If you have nested route, under the same param, changing route will trigger reactivity even if the param doesn't change.

See reproduction app to understand

const route = useRoute()

watchEffect(() => console.log(route.params.myParam)

What is expected?

The params should not change, and nothing should be triggered.

What is actually happening?

the value is still the same, but reactivity change has been triggered, so rerender, and cie.


I've seen it firstly with an asyncComputed from vue-use, and a promise that use the route parameter:

const res = asyncComputed(() => makeCall(route.params.foo))

My call was fired each time a route change, event if nested, and so with the same data.


Other question (I can reopen another issue, but not really a problem here):

Is route a "reactive" object? and so params (if I'm not wrong, nested objects in reactive are also reactives?)? We actually can't destructure it : const {params} = useRoute(), and because we can't (can we?) make "reactive" computed, we have to do const params = computed(() => route.params) and use it with params.value.foo instead of route.params.foo, so no advantage of doing it.

I think some examples with composition api could be cool, because after x months of usage I'm still wondering each time I use it.

Thanks!

edison1105 commented 3 years ago

as a workround:

<router-view :key="$route.path"></router-view>
mathieutu commented 3 years ago

I actually don't see how this could be a workaround 🤔

With that this should be worst as the view will completely rerender when the path change.

posva commented 3 years ago

This is inherent to watchEffect and the fact that each navigation creates a new route object. You need to use a watch on the specific param because watchEffect cannot tell if you depend on route.params, route, or route.params.myParam

watch(() => route.params.myParam, () => {
      paramsHistory.value.push(route.params.myParam);
    });

route is a reactive object so you can extract properties from it with toRef or with a computed

const route = useRoute()
const params = toRef(route, 'params')
const myParam = computed(() => route.params.myParam)
mathieutu commented 3 years ago

So you actually can't use a computed either because the computed will refresh each time the route change, even if the param doesn't change?

fanckush commented 2 years ago

@posva is this true:

So you actually can't use a computed either because the computed will refresh each time the route change, even if the param doesn't change?