Files

4.4 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Use withDirectives for Custom Directives in Render Functions LOW Custom directives require special handling with withDirectives helper best-practice
vue3
render-function
directives
custom-directive

Use withDirectives for Custom Directives in Render Functions

Impact: LOW - To apply custom directives to vnodes in render functions, use the withDirectives helper. Attempting to apply directives through props or other means will not work.

The withDirectives function wraps a vnode and applies directives with their values, arguments, and modifiers.

Task Checklist

  • Import withDirectives from 'vue' when using custom directives
  • Import or define the directive object
  • Pass directive as array: [directive, value, argument, modifiers]
  • Multiple directives can be applied at once

Incorrect:

import { h } from 'vue'

const vFocus = { mounted: (el) => el.focus() }

export default {
  setup() {
    return () => {
      // WRONG: Directives don't work as props
      return h('input', {
        'v-focus': true  // This doesn't work
      })
    }
  }
}

Correct:

import { h, withDirectives } from 'vue'

// Custom directive
const vFocus = {
  mounted: (el) => el.focus()
}

export default {
  setup() {
    return () => {
      // CORRECT: Use withDirectives
      return withDirectives(
        h('input'),
        [[vFocus]]  // Array of directive tuples
      )
    }
  }
}

Directive with Value, Argument, and Modifiers

The directive tuple format: [directive, value, argument, modifiers]

import { h, withDirectives, resolveDirective } from 'vue'

// Directive definition
// Usage in template: <div v-pin:top.animate="200">
const vPin = {
  mounted(el, binding) {
    console.log(binding.value)    // 200
    console.log(binding.arg)      // 'top'
    console.log(binding.modifiers) // { animate: true }

    el.style.position = 'fixed'
    el.style[binding.arg] = binding.value + 'px'
  }
}

export default {
  setup() {
    return () => withDirectives(
      h('div', 'Pinned content'),
      [
        // [directive, value, argument, modifiers]
        [vPin, 200, 'top', { animate: true }]
      ]
    )
  }
}

Multiple Directives

import { h, withDirectives } from 'vue'

const vFocus = { mounted: (el) => el.focus() }
const vTooltip = {
  mounted(el, { value }) {
    el.title = value
  }
}

export default {
  setup() {
    return () => withDirectives(
      h('input', { placeholder: 'Enter text' }),
      [
        [vFocus],
        [vTooltip, 'This is a tooltip']
      ]
    )
  }
}

Using Registered Directives

For globally or locally registered directives, use resolveDirective:

import { h, withDirectives, resolveDirective } from 'vue'

// Assuming v-focus and v-tooltip are registered globally
export default {
  setup() {
    return () => {
      const focus = resolveDirective('focus')
      const tooltip = resolveDirective('tooltip')

      return withDirectives(
        h('input'),
        [
          [focus],
          [tooltip, 'Helpful tip']
        ]
      )
    }
  }
}

Practical Example: Click Outside Directive

import { h, withDirectives, ref } from 'vue'

const vClickOutside = {
  mounted(el, binding) {
    el._clickOutside = (event) => {
      if (!el.contains(event.target)) {
        binding.value(event)
      }
    }
    document.addEventListener('click', el._clickOutside)
  },
  unmounted(el) {
    document.removeEventListener('click', el._clickOutside)
  }
}

export default {
  setup() {
    const isOpen = ref(true)
    const closeDropdown = () => { isOpen.value = false }

    return () => isOpen.value
      ? withDirectives(
          h('div', { class: 'dropdown' }, 'Dropdown content'),
          [[vClickOutside, closeDropdown]]
        )
      : null
  }
}

JSX with Directives

Directives in JSX also require withDirectives:

import { withDirectives } from 'vue'

const vFocus = { mounted: (el) => el.focus() }

export default {
  setup() {
    return () => withDirectives(
      <input placeholder="Search..." />,
      [[vFocus]]
    )
  }
}

Reference