963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
3.6 KiB
3.6 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Avoid Using Custom Directives on Components | HIGH | Custom directives on multi-root components are silently ignored, causing unexpected behavior | gotcha |
|
Avoid Using Custom Directives on Components
Impact: HIGH - Using custom directives on components is not recommended and can lead to unexpected behavior. When applied to a multi-root component, the directive will be ignored and a warning will be thrown. Unlike attributes, directives cannot be passed to a different element with v-bind="$attrs".
Custom directives are designed for direct DOM manipulation on native HTML elements, not Vue components.
Task Checklist
- Only apply custom directives to native HTML elements, not components
- If a component needs directive-like behavior, consider making it part of the component's API
- For components, use props and events instead of directives
- If you must use a directive on a component, ensure it has a single root element
Incorrect:
<template>
<!-- WRONG: Directive on a component - may be ignored -->
<MyComponent v-focus />
<!-- WRONG: Multi-root component - directive is ignored with warning -->
<MultiRootComponent v-highlight />
</template>
<script setup>
import MyComponent from './MyComponent.vue';
import MultiRootComponent from './MultiRootComponent.vue';
// MultiRootComponent.vue has:
// <template>
// <div>First root</div>
// <div>Second root</div>
// </template>
</script>
Correct:
<template>
<!-- CORRECT: Directive on native HTML element -->
<input v-focus />
<!-- CORRECT: Use props/events for component behavior -->
<MyComponent :should-focus="true" />
<!-- CORRECT: Wrap component in element if directive is needed -->
<div v-highlight>
<MyComponent />
</div>
</template>
<script setup>
import MyComponent from './MyComponent.vue';
</script>
When a Directive on Component Works
Directives only work reliably on components with a single root element. The directive applies to the root node, similar to fallthrough attributes:
<!-- SingleRootComponent.vue -->
<template>
<div>I am the only root</div>
</template>
<!-- Parent.vue -->
<template>
<!-- This works because SingleRootComponent has one root -->
<SingleRootComponent v-my-directive />
</template>
However, this is still not recommended because:
- It's fragile - refactoring to multi-root breaks the directive silently
- It's unclear which element receives the directive
- The component author may not expect external DOM manipulation
Better Patterns
Option 1: Component Prop
<!-- FocusableInput.vue -->
<template>
<input ref="inputRef" v-bind="$attrs" />
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
const props = defineProps({
autofocus: Boolean,
});
const inputRef = ref(null);
onMounted(() => {
if (props.autofocus) {
inputRef.value?.focus();
}
});
</script>
<!-- Usage -->
<FocusableInput autofocus />
Option 2: Exposed Method
<!-- FocusableInput.vue -->
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref } from 'vue';
const inputRef = ref(null);
const focus = () => inputRef.value?.focus();
defineExpose({ focus });
</script>
<!-- Parent.vue -->
<template>
<FocusableInput ref="myInput" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
const myInput = ref(null);
onMounted(() => {
myInput.value?.focus();
});
</script>