feat: Complete fleet — 94 skills across 10+ domains
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>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: Treat Directive Hook Arguments as Read-Only
|
||||
impact: MEDIUM
|
||||
impactDescription: Modifying directive arguments causes unpredictable behavior and breaks Vue's internal state
|
||||
type: gotcha
|
||||
tags: [vue3, directives, hooks, read-only, dataset]
|
||||
---
|
||||
|
||||
# Treat Directive Hook Arguments as Read-Only
|
||||
|
||||
**Impact: MEDIUM** - Apart from `el`, you should treat all directive hook arguments (`binding`, `vnode`, `prevVnode`) as read-only and never modify them. Modifying these objects can cause unpredictable behavior and interfere with Vue's internal workings.
|
||||
|
||||
If you need to share information across hooks, use the element's `dataset` attribute or a WeakMap.
|
||||
|
||||
## Task Checklist
|
||||
|
||||
- [ ] Never mutate `binding`, `vnode`, or `prevVnode` arguments
|
||||
- [ ] Use `el.dataset` to share primitive data between hooks
|
||||
- [ ] Use a WeakMap for complex data that needs to persist across hooks
|
||||
- [ ] Only modify `el` (the DOM element) directly
|
||||
|
||||
**Incorrect:**
|
||||
```javascript
|
||||
// WRONG: Mutating binding object
|
||||
const vBadDirective = {
|
||||
mounted(el, binding) {
|
||||
// DON'T DO THIS - modifying binding
|
||||
binding.value = 'modified' // WRONG!
|
||||
binding.customData = 'stored' // WRONG!
|
||||
binding.modifiers.custom = true // WRONG!
|
||||
},
|
||||
updated(el, binding) {
|
||||
// These modifications may be lost or cause errors
|
||||
console.log(binding.customData) // undefined or error
|
||||
}
|
||||
}
|
||||
|
||||
// WRONG: Mutating vnode
|
||||
const vAnotherBadDirective = {
|
||||
mounted(el, binding, vnode) {
|
||||
// DON'T DO THIS
|
||||
vnode.myData = 'stored' // WRONG!
|
||||
vnode.props.modified = true // WRONG!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
```javascript
|
||||
// CORRECT: Use el.dataset for simple data
|
||||
const vWithDataset = {
|
||||
mounted(el, binding) {
|
||||
// Store data on the element's dataset
|
||||
el.dataset.originalValue = binding.value
|
||||
el.dataset.mountedAt = Date.now().toString()
|
||||
},
|
||||
updated(el, binding) {
|
||||
// Access previously stored data
|
||||
console.log('Original:', el.dataset.originalValue)
|
||||
console.log('Current:', binding.value)
|
||||
console.log('Mounted at:', el.dataset.mountedAt)
|
||||
},
|
||||
unmounted(el) {
|
||||
// Clean up dataset if needed
|
||||
delete el.dataset.originalValue
|
||||
delete el.dataset.mountedAt
|
||||
}
|
||||
}
|
||||
|
||||
// CORRECT: Use WeakMap for complex data
|
||||
const directiveState = new WeakMap()
|
||||
|
||||
const vWithWeakMap = {
|
||||
mounted(el, binding) {
|
||||
// Store complex state
|
||||
directiveState.set(el, {
|
||||
originalValue: binding.value,
|
||||
config: binding.arg,
|
||||
mountedAt: Date.now(),
|
||||
callbacks: [],
|
||||
observers: []
|
||||
})
|
||||
},
|
||||
updated(el, binding) {
|
||||
const state = directiveState.get(el)
|
||||
if (state) {
|
||||
console.log('Original:', state.originalValue)
|
||||
console.log('Current:', binding.value)
|
||||
// Can safely modify state object
|
||||
state.updateCount = (state.updateCount || 0) + 1
|
||||
}
|
||||
},
|
||||
unmounted(el) {
|
||||
// WeakMap auto-cleans when element is garbage collected
|
||||
// but explicit cleanup is good for observers/listeners
|
||||
const state = directiveState.get(el)
|
||||
if (state) {
|
||||
state.observers.forEach(obs => obs.disconnect())
|
||||
directiveState.delete(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Element Properties
|
||||
|
||||
```javascript
|
||||
// CORRECT: Use element properties with underscore prefix convention
|
||||
const vTooltip = {
|
||||
mounted(el, binding) {
|
||||
// Store on element with underscore prefix to avoid conflicts
|
||||
el._tooltipInstance = createTooltip(el, binding.value)
|
||||
el._tooltipConfig = { ...binding.modifiers }
|
||||
},
|
||||
updated(el, binding) {
|
||||
// Access and update stored instance
|
||||
if (el._tooltipInstance) {
|
||||
el._tooltipInstance.update(binding.value)
|
||||
}
|
||||
},
|
||||
unmounted(el) {
|
||||
// Clean up
|
||||
if (el._tooltipInstance) {
|
||||
el._tooltipInstance.destroy()
|
||||
delete el._tooltipInstance
|
||||
delete el._tooltipConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What You CAN Modify
|
||||
|
||||
You are allowed to modify the `el` (DOM element) itself:
|
||||
|
||||
```javascript
|
||||
const vHighlight = {
|
||||
mounted(el, binding) {
|
||||
// CORRECT: Modifying el directly is allowed
|
||||
el.style.backgroundColor = binding.value
|
||||
el.classList.add('highlighted')
|
||||
el.setAttribute('data-highlighted', 'true')
|
||||
el.textContent = 'Modified content'
|
||||
},
|
||||
updated(el, binding) {
|
||||
// CORRECT: Update el when binding changes
|
||||
el.style.backgroundColor = binding.value
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Binding Object Properties (Read-Only Reference)
|
||||
|
||||
The `binding` object contains:
|
||||
- `value` - Current value passed to directive (read-only)
|
||||
- `oldValue` - Previous value (only in beforeUpdate/updated) (read-only)
|
||||
- `arg` - Argument passed (e.g., `v-dir:arg`) (read-only)
|
||||
- `modifiers` - Object of modifiers (e.g., `v-dir.foo.bar`) (read-only)
|
||||
- `instance` - Component instance (read-only)
|
||||
- `dir` - Directive definition object (read-only)
|
||||
|
||||
```javascript
|
||||
const vExample = {
|
||||
mounted(el, binding) {
|
||||
// READ these properties, don't modify them
|
||||
console.log(binding.value) // Read: OK
|
||||
console.log(binding.arg) // Read: OK
|
||||
console.log(binding.modifiers) // Read: OK
|
||||
console.log(binding.instance) // Read: OK
|
||||
|
||||
// Store what you need for later
|
||||
el.dataset.directiveArg = binding.arg || ''
|
||||
el.dataset.hasModifierFoo = binding.modifiers.foo ? 'true' : 'false'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
- [Vue.js Custom Directives - Hook Arguments](https://vuejs.org/guide/reusability/custom-directives#hook-arguments)
|
||||
- [MDN - HTMLElement.dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset)
|
||||
Reference in New Issue
Block a user