Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
211 lines
5.4 KiB
Markdown
211 lines
5.4 KiB
Markdown
---
|
|
title: Vue 3 Directive Hooks Renamed from Vue 2
|
|
impact: HIGH
|
|
impactDescription: Using Vue 2 hook names in Vue 3 causes directives to silently fail
|
|
type: gotcha
|
|
tags: [vue3, vue2, migration, directives, hooks, breaking-change]
|
|
---
|
|
|
|
# Vue 3 Directive Hooks Renamed from Vue 2
|
|
|
|
**Impact: HIGH** - Vue 3 renamed all custom directive lifecycle hooks to align with component lifecycle hooks. Using Vue 2 hook names will cause your directives to silently fail since the hooks won't be called. Additionally, the `update` hook was removed entirely.
|
|
|
|
This is a breaking change that requires updating all custom directives when migrating from Vue 2 to Vue 3.
|
|
|
|
## Task Checklist
|
|
|
|
- [ ] Rename `bind` to `beforeMount`
|
|
- [ ] Rename `inserted` to `mounted`
|
|
- [ ] Replace `update` with `beforeUpdate` or `updated` (update was removed)
|
|
- [ ] Rename `componentUpdated` to `updated`
|
|
- [ ] Rename `unbind` to `unmounted`
|
|
- [ ] Add `beforeUpdate` if you need the old `update` behavior
|
|
|
|
## Hook Name Mapping
|
|
|
|
| Vue 2 | Vue 3 |
|
|
|-----------------|----------------|
|
|
| `bind` | `beforeMount` |
|
|
| `inserted` | `mounted` |
|
|
| `update` | **removed** |
|
|
| `componentUpdated` | `updated` |
|
|
| `unbind` | `unmounted` |
|
|
| (none) | `created` |
|
|
| (none) | `beforeUpdate` |
|
|
| (none) | `beforeUnmount`|
|
|
|
|
**Vue 2 (old):**
|
|
```javascript
|
|
// Vue 2 directive - WILL NOT WORK IN VUE 3
|
|
Vue.directive('demo', {
|
|
bind(el, binding, vnode) {
|
|
// Called when directive is first bound to element
|
|
},
|
|
inserted(el, binding, vnode) {
|
|
// Called when element is inserted into parent
|
|
},
|
|
update(el, binding, vnode, oldVnode) {
|
|
// Called on every VNode update (REMOVED in Vue 3)
|
|
},
|
|
componentUpdated(el, binding, vnode, oldVnode) {
|
|
// Called after component and children update
|
|
},
|
|
unbind(el, binding, vnode) {
|
|
// Called when directive is unbound from element
|
|
}
|
|
})
|
|
```
|
|
|
|
**Vue 3 (new):**
|
|
```javascript
|
|
// Vue 3 directive - Correct hook names
|
|
app.directive('demo', {
|
|
created(el, binding, vnode) {
|
|
// NEW: called before element's attributes or event listeners are applied
|
|
},
|
|
beforeMount(el, binding, vnode) {
|
|
// Was: bind
|
|
},
|
|
mounted(el, binding, vnode) {
|
|
// Was: inserted
|
|
},
|
|
beforeUpdate(el, binding, vnode, prevVnode) {
|
|
// NEW: called before the element itself is updated
|
|
},
|
|
updated(el, binding, vnode, prevVnode) {
|
|
// Was: componentUpdated
|
|
// Note: 'update' was removed - use this or beforeUpdate instead
|
|
},
|
|
beforeUnmount(el, binding, vnode) {
|
|
// NEW: called before element is unmounted
|
|
},
|
|
unmounted(el, binding, vnode) {
|
|
// Was: unbind
|
|
}
|
|
})
|
|
```
|
|
|
|
## Migration Examples
|
|
|
|
### Simple Focus Directive
|
|
```javascript
|
|
// Vue 2
|
|
Vue.directive('focus', {
|
|
inserted(el) {
|
|
el.focus()
|
|
}
|
|
})
|
|
|
|
// Vue 3
|
|
app.directive('focus', {
|
|
mounted(el) {
|
|
el.focus()
|
|
}
|
|
})
|
|
```
|
|
|
|
### Directive with Cleanup
|
|
```javascript
|
|
// Vue 2
|
|
Vue.directive('click-outside', {
|
|
bind(el, binding) {
|
|
el._handler = (e) => {
|
|
if (!el.contains(e.target)) binding.value(e)
|
|
}
|
|
document.addEventListener('click', el._handler)
|
|
},
|
|
unbind(el) {
|
|
document.removeEventListener('click', el._handler)
|
|
}
|
|
})
|
|
|
|
// Vue 3
|
|
app.directive('click-outside', {
|
|
beforeMount(el, binding) { // or mounted
|
|
el._handler = (e) => {
|
|
if (!el.contains(e.target)) binding.value(e)
|
|
}
|
|
document.addEventListener('click', el._handler)
|
|
},
|
|
unmounted(el) {
|
|
document.removeEventListener('click', el._handler)
|
|
}
|
|
})
|
|
```
|
|
|
|
### Directive with Updates
|
|
```javascript
|
|
// Vue 2 - using update hook
|
|
Vue.directive('color', {
|
|
bind(el, binding) {
|
|
el.style.color = binding.value
|
|
},
|
|
update(el, binding) {
|
|
// Called on every VNode update
|
|
el.style.color = binding.value
|
|
}
|
|
})
|
|
|
|
// Vue 3 - update removed, use function shorthand or updated
|
|
app.directive('color', (el, binding) => {
|
|
// Function shorthand: called for both mounted AND updated
|
|
el.style.color = binding.value
|
|
})
|
|
|
|
// Or with object syntax
|
|
app.directive('color', {
|
|
mounted(el, binding) {
|
|
el.style.color = binding.value
|
|
},
|
|
updated(el, binding) {
|
|
// Use updated instead of update
|
|
el.style.color = binding.value
|
|
}
|
|
})
|
|
```
|
|
|
|
## Why `update` Was Removed
|
|
|
|
In Vue 2, `update` was called on every VNode update (before children updated), while `componentUpdated` was called after. The distinction was confusing and rarely needed. In Vue 3:
|
|
|
|
- `beforeUpdate` is called before the element updates
|
|
- `updated` is called after the element and all its children have updated
|
|
|
|
```javascript
|
|
// Vue 3 - if you need both before and after
|
|
app.directive('track-updates', {
|
|
beforeUpdate(el, binding) {
|
|
console.log('Before update, old value:', binding.oldValue)
|
|
},
|
|
updated(el, binding) {
|
|
console.log('After update, new value:', binding.value)
|
|
}
|
|
})
|
|
```
|
|
|
|
## vnode Structure Changes
|
|
|
|
In Vue 3, the `vnode` and `prevVnode` arguments also have different structure:
|
|
|
|
```javascript
|
|
// Vue 2
|
|
{
|
|
update(el, binding, vnode, oldVnode) {
|
|
// vnode.context was the component instance
|
|
console.log(vnode.context)
|
|
}
|
|
}
|
|
|
|
// Vue 3
|
|
{
|
|
updated(el, binding, vnode, prevVnode) {
|
|
// Use binding.instance instead of vnode.context
|
|
console.log(binding.instance)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Reference
|
|
- [Vue 3 Migration Guide - Custom Directives](https://v3-migration.vuejs.org/breaking-changes/custom-directives)
|
|
- [Vue.js Custom Directives - Directive Hooks](https://vuejs.org/guide/reusability/custom-directives#directive-hooks)
|