🌐 AI搜索 & 代理 主页
Skip to content

BUG | Directives aren't cleaned up when component unmounts #5001

@romansp

Description

@romansp

Describe the bug

Reproduction steps:

  1. Go to minimal repro
  2. Open Dev Tools to see console.log prints
  3. Press "Toggle component".
  4. Move mouse and notice that mouse coordinates are printed.
  5. Press "Toggle component" again to unmount component and move mouse.

Expected:
Mouse coordinate logs stop printing.

Actual:
Mouse coordinates continue to print after component is unmounted.

I was looking at how directives are implemented in VueUse and noticed that these aren't cleaned when component unmounts.

Currently VueUse directives just setup their effects within mounted hook of directives. For example useMouseInElement directive does the following:

export const vMouseInElement: ObjectDirective<
HTMLElement,
BindingValueFunction | BindingValueArray
> = {
mounted(el, binding) {
const [handler, options] = (typeof binding.value === 'function' ? [binding.value, {}] : binding.value) as BindingValueArray
const state = reactiveOmit(reactive(useMouseInElement(el, options)), 'stop')
watch(state, val => handler(val))
},
}

But when directive's mounted hook runs getCurrentScope is undefined. And VueUse composables heavily rely on it via tryOnScopeDispose utility which is used to configure cleanup routines.

We had a discussion about this issue on discord here: https://discord.com/channels/937808017016119440/937969993964978197/1408143068678455329

Seems like proper solution will require directives to create their own effect scope within a directive instance, run it in mounted and stop it in unmounted. One of the proposed solutions was along these lines

export const vDirective = {
  mounted(target, binding) {
    const scope = effectScope()
    target._scope = scope
    scope.run(()=> {
      // call composable to setup its effects
      // useMouseInElement(/* */)
    })
  },
  unmounted(target){
    target._scope.stop()
  }
};

Reproduction

https://play.vuejs.org/#eNqNU8tu2zAQ/BVWFydALLZIgQKuErgPH1KgD7Q58qJKK5k2RRJ8KAIM/3uXoq1YSpz0Ru4Oh8NZzi75pHXaekgWSWYLw7UjFpzXt0zyRivjyI4YqMieVEY1ZIbQ2dD6ohr9lRsoHG/hAEjpqBq4Zx+ZZLJQ0iL5Wj08HrkJ3BdVLixcMpnRqADvxo2DRovcAe4IyUre9gtc/vXOKUmWheDF9oYlU8o3owJLbu9VXQsgBepSEqTLaKQ4Eo6f0c55NWVlCaFRB41CMnoiL7lKoiHzJtfpxiqJbu4CnB0aliUL0ldCbYmeeAu0UAaZF1hZO6ftglIv9bZOUSg9xSzfXacf0reUyxK6tNkg29WUy65zA+VrbBH1P3yDWb30lxUekc/wBto9k3u0yFn8ARWvJwaF41yA+akdxx8yMioXQj1862vOeBhEFmsots/UN7aLan8ZsGDC3Iaey00NLrZXf35Ah+uh2ajSi8MszjR/g1XCB40R9tnLEmWf4Hq1d/28uazv7apzIO3xUUFo70aPZwm6F/7duac/yr1O35+4+CRcLwW3/a5wQndyJaDBAQ0hfjo6zDSTlZdIi9FScnzwAsXYyyguxFgJSIWq+3LaYXJR3CvhxVg1gXPO5RwiK4ZsehGGNRoQnknKIX5nk7f/Bx9hoVE=

System Info

System:
    OS: macOS 15.6.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 148.69 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.17.1 - ~/.nvm/versions/node/v22.17.1/bin/node
    Yarn: 4.9.3 - ~/.nvm/versions/node/v22.17.1/bin/yarn
    npm: 10.9.2 - ~/.nvm/versions/node/v22.17.1/bin/npm
    bun: 1.2.19 - ~/.bun/bin/bun
  Browsers:
    Chrome: 139.0.7258.155
    Safari: 18.6
  npmPackages:
    @vueuse/core: 13.8.0 => 13.8.0 
    @vueuse/gesture: ^2.0.0 => 2.0.0 
    vue: ^3.5.20 => 3.5.20

Used Package Manager

npm

Validations

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions