@pythoughts/vue-skills-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/index.mjs +139 -0
- package/package.json +34 -0
- package/skills/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +350 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +346 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-debug-guides/SKILL.md +205 -0
- package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Specify Transition Type When Mixing CSS Transitions and Animations
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Vue may detect the wrong transition end event when both CSS transitions and animations are applied, causing timing issues
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, transition, animation, css, type, timing]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Specify Transition Type When Mixing CSS Transitions and Animations
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When you have both CSS transitions and CSS animations applied to the same element (for example, a Vue-triggered animation combined with a hover transition effect), Vue cannot automatically determine which end event to listen for. You must explicitly tell Vue which type to prioritize using the `type` attribute with a value of either `"animation"` or `"transition"`.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Check if your element has both `transition` and `animation` CSS properties
|
|
16
|
+
- [ ] Determine which timing should control when Vue considers the transition complete
|
|
17
|
+
- [ ] Add `type="animation"` or `type="transition"` to the `<Transition>` component
|
|
18
|
+
- [ ] The type should match whichever animation/transition is longer or more important
|
|
19
|
+
|
|
20
|
+
**Problematic Code:**
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<!-- BAD: Both transition and animation present, Vue might pick wrong end event -->
|
|
24
|
+
<Transition name="bounce">
|
|
25
|
+
<div v-if="show" class="box">
|
|
26
|
+
Hover me for additional effect
|
|
27
|
+
</div>
|
|
28
|
+
</Transition>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
/* Vue-triggered CSS animation */
|
|
33
|
+
.bounce-enter-active {
|
|
34
|
+
animation: bounce-in 0.5s;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.bounce-leave-active {
|
|
38
|
+
animation: bounce-out 0.3s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@keyframes bounce-in {
|
|
42
|
+
0% { transform: scale(0); }
|
|
43
|
+
50% { transform: scale(1.2); }
|
|
44
|
+
100% { transform: scale(1); }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes bounce-out {
|
|
48
|
+
0% { transform: scale(1); }
|
|
49
|
+
100% { transform: scale(0); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Additional hover transition on same element */
|
|
53
|
+
.box {
|
|
54
|
+
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.box:hover {
|
|
58
|
+
background-color: #f0f0f0;
|
|
59
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Correct Code:**
|
|
65
|
+
```vue
|
|
66
|
+
<template>
|
|
67
|
+
<!-- GOOD: Explicitly specify that animation controls timing -->
|
|
68
|
+
<Transition name="bounce" type="animation">
|
|
69
|
+
<div v-if="show" class="box">
|
|
70
|
+
Hover me for additional effect
|
|
71
|
+
</div>
|
|
72
|
+
</Transition>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
/* Vue-triggered CSS animation - this is what we care about */
|
|
77
|
+
.bounce-enter-active {
|
|
78
|
+
animation: bounce-in 0.5s;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.bounce-leave-active {
|
|
82
|
+
animation: bounce-out 0.3s;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@keyframes bounce-in {
|
|
86
|
+
0% { transform: scale(0); }
|
|
87
|
+
50% { transform: scale(1.2); }
|
|
88
|
+
100% { transform: scale(1); }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes bounce-out {
|
|
92
|
+
0% { transform: scale(1); }
|
|
93
|
+
100% { transform: scale(0); }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Additional hover transition - unrelated to Vue transition timing */
|
|
97
|
+
.box {
|
|
98
|
+
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.box:hover {
|
|
102
|
+
background-color: #f0f0f0;
|
|
103
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## When to Use Each Type
|
|
109
|
+
|
|
110
|
+
### Use `type="animation"` when:
|
|
111
|
+
- Your enter/leave effects use `@keyframes` animations
|
|
112
|
+
- The animation is longer than any transitions
|
|
113
|
+
- You want precise control over multi-step animations
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<Transition name="fancy" type="animation">
|
|
117
|
+
<div v-if="show" class="animated-element" />
|
|
118
|
+
</Transition>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.fancy-enter-active {
|
|
122
|
+
animation: fancy-entrance 1s ease-out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.animated-element {
|
|
126
|
+
/* This shorter transition should not affect timing */
|
|
127
|
+
transition: color 0.2s;
|
|
128
|
+
}
|
|
129
|
+
</style>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Use `type="transition"` when:
|
|
133
|
+
- Your enter/leave effects use CSS `transition` property
|
|
134
|
+
- You have decorative animations that shouldn't affect timing
|
|
135
|
+
|
|
136
|
+
```vue
|
|
137
|
+
<Transition name="slide" type="transition">
|
|
138
|
+
<div v-if="show" class="sliding-element" />
|
|
139
|
+
</Transition>
|
|
140
|
+
|
|
141
|
+
<style>
|
|
142
|
+
.slide-enter-active,
|
|
143
|
+
.slide-leave-active {
|
|
144
|
+
transition: transform 0.3s ease;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.slide-enter-from,
|
|
148
|
+
.slide-leave-to {
|
|
149
|
+
transform: translateX(-100%);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Decorative infinite animation should not affect timing */
|
|
153
|
+
.sliding-element {
|
|
154
|
+
animation: pulse 2s infinite;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@keyframes pulse {
|
|
158
|
+
0%, 100% { opacity: 1; }
|
|
159
|
+
50% { opacity: 0.8; }
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Common Symptoms Without Type Specification
|
|
165
|
+
|
|
166
|
+
1. Transition ends too early (element snaps to final position)
|
|
167
|
+
2. Transition hangs or takes too long to complete
|
|
168
|
+
3. Element disappears before animation finishes
|
|
169
|
+
4. CSS classes remain applied after transition should be complete
|
|
170
|
+
|
|
171
|
+
## Reference
|
|
172
|
+
- [Vue.js Transition Documentation](https://vuejs.org/guide/built-ins/transition.html#css-based-transitions)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Unmount Hooks May Not Fire Inside Transitions During Fast Replacement
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Components inside transitions can be destroyed without unmount hooks firing under race conditions
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, lifecycle, transition, onUnmounted, unmounted, cleanup, race-condition]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Unmount Hooks May Not Fire Inside Transitions During Fast Replacement
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When a component inside a `<transition>` is replaced by another component during the transition's loading phase, the unmount hooks (`onBeforeUnmount`, `onUnmounted`) may not be called even though the component is removed from the DOM. This can cause memory leaks and resource leaks from unclean side effects.
|
|
12
|
+
|
|
13
|
+
This is a known edge case that occurs when the timing is specific - if a parent component with a child inside a transition is replaced while the child is still mounting. The child's mount hooks fire, but unmount hooks never do.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Be aware that unmount hooks are not 100% guaranteed inside transitions
|
|
18
|
+
- [ ] For critical cleanup, consider alternative cleanup strategies
|
|
19
|
+
- [ ] Use `mode="out-in"` on transitions to ensure old component fully unmounts before new mounts
|
|
20
|
+
- [ ] For essential resources, consider cleanup at parent component level
|
|
21
|
+
- [ ] Test component replacement scenarios during development
|
|
22
|
+
|
|
23
|
+
**Problematic Scenario:**
|
|
24
|
+
```vue
|
|
25
|
+
<!-- Parent component with lazy-loaded child in transition -->
|
|
26
|
+
<template>
|
|
27
|
+
<transition>
|
|
28
|
+
<Suspense>
|
|
29
|
+
<component :is="currentComponent" />
|
|
30
|
+
</Suspense>
|
|
31
|
+
</transition>
|
|
32
|
+
</template>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// Child component - unmount hooks may not fire if parent changes quickly
|
|
37
|
+
export default {
|
|
38
|
+
setup() {
|
|
39
|
+
const socket = new WebSocket('wss://example.com')
|
|
40
|
+
|
|
41
|
+
onMounted(() => {
|
|
42
|
+
console.log('Mounted - this will run')
|
|
43
|
+
socket.connect()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
onUnmounted(() => {
|
|
47
|
+
// WARNING: This may NOT run if component is inside transition
|
|
48
|
+
// and parent navigates away during mounting phase!
|
|
49
|
+
console.log('Unmounted - might not run')
|
|
50
|
+
socket.close()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Safer Patterns:**
|
|
57
|
+
```vue
|
|
58
|
+
<!-- SAFER: Use out-in mode to ensure proper sequencing -->
|
|
59
|
+
<template>
|
|
60
|
+
<transition mode="out-in">
|
|
61
|
+
<component :is="currentComponent" :key="currentKey" />
|
|
62
|
+
</transition>
|
|
63
|
+
</template>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// SAFER: Cleanup at parent level for critical resources
|
|
68
|
+
// Parent component
|
|
69
|
+
export default {
|
|
70
|
+
setup() {
|
|
71
|
+
const childSocket = ref(null)
|
|
72
|
+
|
|
73
|
+
// Parent controls resource lifecycle
|
|
74
|
+
provide('registerSocket', (socket) => {
|
|
75
|
+
childSocket.value = socket
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
onUnmounted(() => {
|
|
79
|
+
// Parent ensures cleanup even if child unmount hook doesn't fire
|
|
80
|
+
childSocket.value?.close()
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Child component
|
|
86
|
+
export default {
|
|
87
|
+
setup() {
|
|
88
|
+
const registerSocket = inject('registerSocket')
|
|
89
|
+
const socket = new WebSocket('wss://example.com')
|
|
90
|
+
|
|
91
|
+
// Register with parent for backup cleanup
|
|
92
|
+
registerSocket(socket)
|
|
93
|
+
|
|
94
|
+
onMounted(() => {
|
|
95
|
+
socket.connect()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
onUnmounted(() => {
|
|
99
|
+
socket.close() // Still attempt cleanup here
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// SAFER: Use AbortController pattern for cancellable operations
|
|
107
|
+
export default {
|
|
108
|
+
setup() {
|
|
109
|
+
const abortController = new AbortController()
|
|
110
|
+
|
|
111
|
+
onMounted(() => {
|
|
112
|
+
fetch('/api/data', { signal: abortController.signal })
|
|
113
|
+
.then(handleData)
|
|
114
|
+
.catch(err => {
|
|
115
|
+
if (err.name !== 'AbortError') {
|
|
116
|
+
handleError(err)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
onUnmounted(() => {
|
|
122
|
+
// If this doesn't fire, request continues but response is ignored
|
|
123
|
+
// Not a memory leak - just potentially wasted network call
|
|
124
|
+
abortController.abort()
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Testing for This Issue
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// Test by rapidly switching components during async loading
|
|
134
|
+
async function testUnmountHooks() {
|
|
135
|
+
// Mount component A (has async setup)
|
|
136
|
+
await mountComponent('A')
|
|
137
|
+
|
|
138
|
+
// Immediately switch to B before A finishes mounting
|
|
139
|
+
await mountComponent('B')
|
|
140
|
+
|
|
141
|
+
// Check if A's unmount hooks fired
|
|
142
|
+
// They may not have!
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Reference
|
|
147
|
+
- [Vue.js GitHub Issue #6260](https://github.com/vuejs/core/issues/6260)
|
|
148
|
+
- [Vue.js Transition](https://vuejs.org/guide/built-ins/transition.html)
|
|
149
|
+
- [Vue.js Lifecycle Hooks](https://vuejs.org/guide/essentials/lifecycle.html)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Boolean Props Default to false, Not undefined
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: TypeScript expects optional boolean to be undefined but Vue defaults it to false, causing type confusion
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, typescript, props, boolean, defineProps]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Boolean Props Default to false, Not undefined
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When using type-based `defineProps`, optional boolean props (marked with `?`) behave differently than TypeScript expects. Vue treats boolean props specially: an absent boolean prop defaults to `false`, not `undefined`. This can cause confusion when TypeScript thinks the type is `boolean | undefined`.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Understand that Vue's boolean casting makes absent booleans `false`
|
|
16
|
+
- [ ] Use `withDefaults()` to be explicit about boolean defaults
|
|
17
|
+
- [ ] Consider using non-boolean types if `undefined` is a meaningful state
|
|
18
|
+
- [ ] Document this Vue-specific behavior for your team
|
|
19
|
+
|
|
20
|
+
## The Gotcha
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
interface Props {
|
|
25
|
+
disabled?: boolean // TypeScript sees: boolean | undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = defineProps<Props>()
|
|
29
|
+
|
|
30
|
+
// TypeScript thinks props.disabled could be undefined
|
|
31
|
+
if (props.disabled === undefined) {
|
|
32
|
+
console.log('This will NEVER run!')
|
|
33
|
+
// Vue's boolean casting means disabled is false, not undefined
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<!-- When used without the prop -->
|
|
39
|
+
<MyComponent />
|
|
40
|
+
<!-- disabled is false, NOT undefined -->
|
|
41
|
+
</template>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Why This Happens
|
|
45
|
+
|
|
46
|
+
Vue has special "boolean casting" behavior inherited from HTML boolean attributes:
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<!-- All of these make disabled = true -->
|
|
50
|
+
<MyComponent disabled />
|
|
51
|
+
<MyComponent :disabled="true" />
|
|
52
|
+
<MyComponent disabled="" />
|
|
53
|
+
|
|
54
|
+
<!-- This makes disabled = false (NOT undefined) -->
|
|
55
|
+
<MyComponent />
|
|
56
|
+
|
|
57
|
+
<!-- Explicit false -->
|
|
58
|
+
<MyComponent :disabled="false" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This is by design to match how HTML works:
|
|
62
|
+
```html
|
|
63
|
+
<!-- HTML: presence means true, absence means false -->
|
|
64
|
+
<button disabled>Can't click</button>
|
|
65
|
+
<button>Can click</button>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Solutions
|
|
69
|
+
|
|
70
|
+
### Solution 1: Be Explicit with withDefaults
|
|
71
|
+
|
|
72
|
+
Make your intention clear:
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
interface Props {
|
|
77
|
+
disabled?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Explicitly document the default
|
|
81
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
82
|
+
disabled: false // Now it's clear this defaults to false
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Solution 2: Use a Three-State Type
|
|
88
|
+
|
|
89
|
+
If you actually need to distinguish "not set" from "explicitly false":
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<script setup lang="ts">
|
|
93
|
+
interface Props {
|
|
94
|
+
// Use a union type instead of optional boolean
|
|
95
|
+
state?: 'enabled' | 'disabled' | undefined
|
|
96
|
+
|
|
97
|
+
// Or use undefined explicitly
|
|
98
|
+
toggleState?: boolean | undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
102
|
+
state: undefined, // Can actually be undefined
|
|
103
|
+
toggleState: undefined
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Now you can check for undefined
|
|
107
|
+
if (props.state === undefined) {
|
|
108
|
+
// Use parent's state
|
|
109
|
+
} else if (props.state === 'disabled') {
|
|
110
|
+
// Explicitly disabled
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Solution 3: Use null for "Not Set"
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<script setup lang="ts">
|
|
119
|
+
interface Props {
|
|
120
|
+
// null = not set, false = explicitly off, true = explicitly on
|
|
121
|
+
selected: boolean | null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
125
|
+
selected: null
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Three distinct states
|
|
129
|
+
if (props.selected === null) {
|
|
130
|
+
console.log('Selection not specified')
|
|
131
|
+
} else if (props.selected) {
|
|
132
|
+
console.log('Selected')
|
|
133
|
+
} else {
|
|
134
|
+
console.log('Explicitly not selected')
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Boolean Casting Order
|
|
140
|
+
|
|
141
|
+
Vue also has special behavior when Boolean and String are both valid:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Order matters in runtime declaration!
|
|
145
|
+
defineProps({
|
|
146
|
+
// Boolean first: empty string becomes true
|
|
147
|
+
disabled: [Boolean, String]
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// <MyComponent disabled /> → disabled = true
|
|
151
|
+
// <MyComponent disabled="" /> → disabled = true
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
defineProps({
|
|
156
|
+
// String first: empty string stays as string
|
|
157
|
+
disabled: [String, Boolean]
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// <MyComponent disabled /> → disabled = ''
|
|
161
|
+
// <MyComponent disabled="" /> → disabled = ''
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
With type-based declaration, Boolean always takes priority for absent props.
|
|
165
|
+
|
|
166
|
+
## Common Bug Pattern
|
|
167
|
+
|
|
168
|
+
```vue
|
|
169
|
+
<!-- Parent.vue -->
|
|
170
|
+
<script setup lang="ts">
|
|
171
|
+
const userPreferences = ref({
|
|
172
|
+
darkMode: undefined as boolean | undefined
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Fetch preferences...
|
|
176
|
+
onMounted(async () => {
|
|
177
|
+
userPreferences.value = await fetchPreferences()
|
|
178
|
+
})
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<!-- Bug: undefined becomes false, not "inherit system preference" -->
|
|
183
|
+
<ThemeToggle :darkMode="userPreferences.darkMode" />
|
|
184
|
+
</template>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Fix:**
|
|
188
|
+
|
|
189
|
+
```vue
|
|
190
|
+
<script setup lang="ts">
|
|
191
|
+
const userPreferences = ref<{
|
|
192
|
+
darkMode: boolean | null
|
|
193
|
+
}>({
|
|
194
|
+
darkMode: null // Use null for "not yet loaded"
|
|
195
|
+
})
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<template>
|
|
199
|
+
<!-- Now ThemeToggle can distinguish between null and false -->
|
|
200
|
+
<ThemeToggle :darkMode="userPreferences.darkMode" />
|
|
201
|
+
</template>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## TypeScript Type Accuracy
|
|
205
|
+
|
|
206
|
+
The Vue type system handles this, but it can be confusing:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
interface Props {
|
|
210
|
+
disabled?: boolean
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const props = defineProps<Props>()
|
|
214
|
+
|
|
215
|
+
// At compile time: boolean | undefined
|
|
216
|
+
// At runtime: boolean (never undefined due to Vue's boolean casting)
|
|
217
|
+
|
|
218
|
+
// TypeScript is technically "wrong" here, but the withDefaults usage
|
|
219
|
+
// or explicit false default can help align expectations
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Reference
|
|
223
|
+
- [Vue.js Props - Boolean Casting](https://vuejs.org/guide/components/props.html#boolean-casting)
|
|
224
|
+
- [GitHub Issue: Boolean props default to false](https://github.com/vuejs/core/issues/8576)
|
|
225
|
+
- [TypeScript Vue 3 Props](https://madewithlove.com/blog/typescript-vue-3-and-strongly-typed-props/)
|