core icon indicating copy to clipboard operation
core copied to clipboard

`watchEffect` within `effectScope` is removed when component is unmounted

Open posva opened this issue 2 years ago • 4 comments
trafficstars

Vue version

edge

Link to minimal reproduction

https://sfc.vuejs.org/#eNqFVE+vmzAM/yoel1KJkntHq03brrtUu3GhqWl5LyRREto39fHd5yTAo1R6ExJ2Yvvnf7HvyXet82uHyTYpHLZaVA73pQQoTs0VuKis3ZUJvyB/LZMgIFEjdefA/dU4yo7qrUzgumnVCQVd2ou60UVz+uDZaC2qIwqolZlk+wMRcBeEghPA/uCUwT8WTcHCuWDBJsbFKDDiAj8pkuumfvRVsFk+dLTcNNqBRddpEJU8k7qzIamm1co4uIPBGnqojWphRUVZfZ1kH56iNGfh5ie2ypfPa5aSK2nJg09m57FSZzpck6hg0XuIJMmSCLppK52/WCWp+HefTzkIKKothBt/R/D+XCYX57TdMmZr7n2+2FyZMyMuN510TYs52nZzNOpGcRJwmWQzDEaXVzQbg/KEBs1nmAvVJ1wP25eyp1SsL0PuLOUwL2MGt8rxy0B+1TVylwEGeuBK47LO/hNI1fNCOgfeYxOPbwG47iR3jZLQWfzRkbANPUjXsVZNDWmwWFMArjNysoeISk2J9P19HsnUJVLzBqOar2pK4Lv92IvY31pU56G/dSXsYEpVDv9Zvo/G0VwJzIU6p6uZXkBcZYHk10pQNNGkH+gMOfVKGfwX+VPMyA1FSieUAD1qhaQC8cy8oNT38IgfBsDvj2HCZrMVp20+YdS6YJctmjg9h5yNL2o+U/cYHEg800CfSHv39AqWc/a8z8YNdOyco2f0jYuGv1KoQ0e/eDptOYDfwdmwk7xsWEcDCosw41GbUeJVaX5jzNBTtaL+qDGusMWGSvp/xP3dXw==

Steps to reproduce

  • Click the "negate button" -> see multiple logs
  • untick the checkbox and tick it again (triggers unmount + mount of component)
  • Click the negate button again -> see only one log

What is expected?

watchEffect should trigger because it's inside of a detached effectScope

What is actually happening?

watchEffect isn't run anymore after the wrapping component unmounts

System Info

na

Any additional comments?

From https://github.com/vuejs/pinia/issues/1862

posva avatar Dec 11 '22 09:12 posva

https://github.com/vuejs/core/blob/11bd8db768f1f3587ed7e820357369551c081c60/packages/runtime-core/src/apiWatch.ts#L232-L236

let scope;
let store;
export function useCustomStore() {
-  if (store) return store; // comment this line will works fine.
  scope = scope || effectScope(true);
  store = scope.run(() => {
    const flag = ref(false);

+ The root cause is that an unmounted instance is cached in `watchEffect`. 
+ When the component is re-mount, we create a new instance of the component. 
+ But because `if (store) return store;` causes `watchEffect` not to be re-executed, 
+ so its internal instance is the component instance that has been unmounted.
    watchEffect(() => {
      console.log('watchEffect flag', flag.value)
    })
    
    watch(flag, () => {
      console.log('watch flag', flag.value)
    })

    return ({
      flag,
    });
  });

  return store
}

edison1105 avatar Dec 11 '22 13:12 edison1105

But the watchEffect should be attached to the effectScope like a regular watch The return is intentional to simulate a store

posva avatar Dec 11 '22 13:12 posva

@posva Actuality, the instance in the regular watch callback is also incorrect and is unmounted. It can execute because we don't determine if the instance is unmounted like watchEffect does.

watch(flag, () => {
  console.log('watch flag', flag.value)
})

edison1105 avatar Dec 11 '22 13:12 edison1105

I think watch(Effect)() should care about the currentInstance only if the activeEffectScope === instance.scope during the watcher's creation. But currently, the getter created for a watchEffect() will return early if the instance it was created "in" has unmounted.

Would you agree?

LinusBorg avatar Dec 11 '22 16:12 LinusBorg