Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to pass reactive arguments to custom directives #8034

Closed
katerlouis opened this issue Apr 5, 2023 · 5 comments
Closed

Add a way to pass reactive arguments to custom directives #8034

katerlouis opened this issue Apr 5, 2023 · 5 comments
Labels
✨ feature request New feature or request

Comments

@katerlouis
Copy link

What problem does this feature solve?

I created a minimal reproduction of the issue. I know that this minimal reproduction can be solved with both composables and component just fine, but please believe me that for my use case a directive is the most pleasant developer experience.

Arguments in custom directives can be dynamic, but unfortunately they seem to get unrefd in the process?

https://codesandbox.io/s/reactive-argument-in-custom-directives-6bpg7t?file=/src/directive.js
Here I would like the background color on hover to react to the state change when toggling.

What does the proposed API look like?

I am clueless to how the API would need to change, since we already do pass a reactive property.
Maybe when we pass a computed property it can keep its reactivity?

@katerlouis katerlouis added the ✨ feature request New feature or request label Apr 5, 2023
@LinusBorg
Copy link
Member

LinusBorg commented Apr 5, 2023

refs are unwrapped in templates, that's not going to change in the current major version at least. You can only work around that. i.e. by using a plain wrapper object (which is not being unwrapped).

Also, custom directives are not set up to deal with reactive effects from watch() etc. calls to watch() will not be unmounted automatically as you did not call in in setup - you called it in a custom directive's hook. Even if they were, they would only be cleaned up when the component unmounts, which means that if the element is unmounted by v-if, the watcher stays active. not what you want, most of the time.

So right now you have watchers that stay in memory forever.

A more typical implementation of such a custom directive would look something like this:

const elsColors = new WeakMap()
const elsHandlers = new WeakMap()

export default {
  mounted(el, binding) {
    elsColors.set(el, binding.value)
    const handler = (event) => {
      console.log('handler', event.type)
      el.style.backgroundColor = event.type === 'mouseenter'
        ? elsColors.get(el)
        : ''
    }
    elsHandlers.set(el, handler)
    el.addEventListener('mouseenter', handler)
    el.addEventListener('mouseleave', handler)
  },

  updated(el, binding) {
    elsColors.set(el, binding.value)
    // I can't find a way to tell `mounted()` to use a different backgroundColor from here
    // Also: it would be tedious to manually keep state in sync using updated-hook
  },

  beforeUnmoun(el) {
    const handler = elsHandlers.get(el)
    el.removeEventListener('mouseenter', handler)
    el.removeEventListener('mouseleave', handler)
  }
};

For most instances, you could likely even drop the whole handling of the unmounting as the event listeners should stop working when the element is being unmounted anyway, and disappears with GC. However, Vue could re-use that element for other purposes (i.e. in a v-if /v-else scenario), so cleaning up like this is safer.

@katerlouis
Copy link
Author

Very interesting; I've tried something similar, since it felt wrong to use reactive properties in directives, but did not know about WeakMap

I'm wondering what the prefix els is for?

Should you clean up inside elsColors and elsHandlers aswell in beforeUnmount()?

PS: in case somebody copies your code: beforeUnmoun is missing the t

@LinusBorg
Copy link
Member

el(ement)s

You dont have to clean up the maps because they are WeakMaps. When the element is being garbage collected, the maps entry is as well.

@katerlouis
Copy link
Author

What was new to me is that a DOM element can be key of an object; nice :)

@LinusBorg
Copy link
Member

It can be the key of a Map, yes.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
✨ feature request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants