@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,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineModel Default Value Can Cause Parent-Child Desync
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Default values in defineModel don't sync back to parent, causing state inconsistency
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, v-model, defineModel, components, props, two-way-binding]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# defineModel Default Value Can Cause Parent-Child Desync
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When using `defineModel()` with a default value and the parent doesn't provide a value, the parent and child components will have different values. The parent's ref stays `undefined` while the child uses the default, breaking the two-way binding contract.
|
|
12
|
+
|
|
13
|
+
This subtle bug can cause confusing behavior where the parent component shows one value while the child shows another, and updates may not propagate correctly.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always provide initial values from the parent when using v-model
|
|
18
|
+
- [ ] Don't rely on defineModel defaults as the primary source of truth
|
|
19
|
+
- [ ] If defaults are needed, also set them in the parent component
|
|
20
|
+
- [ ] Test components with and without v-model props provided
|
|
21
|
+
|
|
22
|
+
**Problem - Parent and child out of sync:**
|
|
23
|
+
```html
|
|
24
|
+
<!-- ChildComponent.vue -->
|
|
25
|
+
<script setup>
|
|
26
|
+
// Default value of 1 if parent doesn't provide value
|
|
27
|
+
const model = defineModel({ default: 1 })
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<input v-model="model" type="number">
|
|
32
|
+
<!-- Shows: 1 (from default) -->
|
|
33
|
+
</template>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<!-- ParentComponent.vue -->
|
|
38
|
+
<script setup>
|
|
39
|
+
import { ref } from 'vue'
|
|
40
|
+
import ChildComponent from './ChildComponent.vue'
|
|
41
|
+
|
|
42
|
+
// PROBLEM: Parent ref is undefined, not synced with child's default
|
|
43
|
+
const myValue = ref() // undefined
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<ChildComponent v-model="myValue" />
|
|
48
|
+
|
|
49
|
+
<!-- DESYNC: Child shows 1, but parent shows undefined -->
|
|
50
|
+
<p>Parent value: {{ myValue }}</p> <!-- Shows: undefined -->
|
|
51
|
+
|
|
52
|
+
<!-- Even after child changes value, parent may not update correctly -->
|
|
53
|
+
</template>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Solution 1 - Always provide initial value from parent:**
|
|
57
|
+
```html
|
|
58
|
+
<!-- ParentComponent.vue -->
|
|
59
|
+
<script setup>
|
|
60
|
+
import { ref } from 'vue'
|
|
61
|
+
import ChildComponent from './ChildComponent.vue'
|
|
62
|
+
|
|
63
|
+
// CORRECT: Parent provides the initial value
|
|
64
|
+
const myValue = ref(1) // Match the expected default
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<ChildComponent v-model="myValue" />
|
|
69
|
+
<p>Parent value: {{ myValue }}</p> <!-- Shows: 1, stays in sync -->
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Solution 2 - Child emits default on mount (if parent control not possible):**
|
|
74
|
+
```html
|
|
75
|
+
<!-- ChildComponent.vue -->
|
|
76
|
+
<script setup>
|
|
77
|
+
import { onMounted } from 'vue'
|
|
78
|
+
|
|
79
|
+
const model = defineModel({ default: 1 })
|
|
80
|
+
|
|
81
|
+
// Sync default value back to parent on mount
|
|
82
|
+
onMounted(() => {
|
|
83
|
+
if (model.value === 1) { // Is using default
|
|
84
|
+
// Force emit to sync parent
|
|
85
|
+
model.value = 1
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<input v-model="model" type="number">
|
|
92
|
+
</template>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Solution 3 - Use required prop or explicit undefined handling:**
|
|
96
|
+
```html
|
|
97
|
+
<!-- ChildComponent.vue -->
|
|
98
|
+
<script setup>
|
|
99
|
+
import { computed } from 'vue'
|
|
100
|
+
|
|
101
|
+
// Mark as required - TypeScript will warn if not provided
|
|
102
|
+
const model = defineModel({ required: true })
|
|
103
|
+
|
|
104
|
+
// Or handle undefined explicitly
|
|
105
|
+
const safeModel = computed({
|
|
106
|
+
get: () => model.value ?? 1, // Provide fallback
|
|
107
|
+
set: (val) => { model.value = val }
|
|
108
|
+
})
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<input v-model="safeModel" type="number">
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Best Practice - Document expected initial values:**
|
|
117
|
+
```html
|
|
118
|
+
<!-- ChildComponent.vue -->
|
|
119
|
+
<script setup>
|
|
120
|
+
/**
|
|
121
|
+
* @prop modelValue - The numeric value (parent should initialize to 1 or desired default)
|
|
122
|
+
*/
|
|
123
|
+
const model = defineModel({
|
|
124
|
+
type: Number,
|
|
125
|
+
default: 1,
|
|
126
|
+
// Adding validator helps catch issues in development
|
|
127
|
+
validator: (value) => {
|
|
128
|
+
if (value === undefined) {
|
|
129
|
+
console.warn('ChildComponent: v-model value is undefined. Provide initial value from parent.')
|
|
130
|
+
}
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Reference
|
|
138
|
+
- [Vue.js Component v-model](https://vuejs.org/guide/components/v-model.html)
|
|
139
|
+
- [Vue School - defineModel Guide](https://vueschool.io/articles/vuejs-tutorials/v-model-and-definemodel-a-comprehensive-guide-to-two-way-binding-in-vue-js-3/)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineEmits Must Be Used at Top Level of script setup
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Using defineEmits inside functions causes compilation errors - macros must be at module scope
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, defineEmits, script-setup, macros, composition-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# defineEmits Must Be Used at Top Level of script setup
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - The `defineEmits()` macro can only be used directly within `<script setup>` at the top level. It cannot be placed inside functions, conditionals, or any other nested scope. Vue's compiler hoists these macros to module scope during compilation.
|
|
12
|
+
|
|
13
|
+
This applies to all Vue macros: `defineProps`, `defineEmits`, `defineExpose`, `defineOptions`, and `defineSlots`.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Place `defineEmits()` directly in `<script setup>`, not inside functions
|
|
18
|
+
- [ ] Do not wrap macro calls in conditionals or loops
|
|
19
|
+
- [ ] Do not reference local variables in macro arguments
|
|
20
|
+
- [ ] Store the emit function and reuse it throughout the component
|
|
21
|
+
|
|
22
|
+
## The Problem
|
|
23
|
+
|
|
24
|
+
**Incorrect - Inside a function:**
|
|
25
|
+
```vue
|
|
26
|
+
<script setup>
|
|
27
|
+
function useEvents() {
|
|
28
|
+
// ERROR: defineEmits cannot be used inside a function
|
|
29
|
+
const emit = defineEmits(['submit', 'cancel'])
|
|
30
|
+
return emit
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const emit = useEvents() // This fails at compile time
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Incorrect - Inside a conditional:**
|
|
38
|
+
```vue
|
|
39
|
+
<script setup>
|
|
40
|
+
if (someCondition) {
|
|
41
|
+
// ERROR: Cannot use defineEmits in conditional
|
|
42
|
+
const emit = defineEmits(['eventA'])
|
|
43
|
+
} else {
|
|
44
|
+
const emit = defineEmits(['eventB'])
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Incorrect - Referencing local variables:**
|
|
50
|
+
```vue
|
|
51
|
+
<script setup>
|
|
52
|
+
const eventNames = ['submit', 'cancel']
|
|
53
|
+
|
|
54
|
+
// ERROR: Cannot reference local variables
|
|
55
|
+
const emit = defineEmits(eventNames)
|
|
56
|
+
</script>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Correct Usage
|
|
60
|
+
|
|
61
|
+
**Correct - Top level declaration:**
|
|
62
|
+
```vue
|
|
63
|
+
<script setup>
|
|
64
|
+
// CORRECT: defineEmits at top level of script setup
|
|
65
|
+
const emit = defineEmits(['submit', 'cancel', 'update'])
|
|
66
|
+
|
|
67
|
+
function handleSubmit() {
|
|
68
|
+
emit('submit', data)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleCancel() {
|
|
72
|
+
emit('cancel')
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Correct - With TypeScript types:**
|
|
78
|
+
```vue
|
|
79
|
+
<script setup lang="ts">
|
|
80
|
+
// CORRECT: Type-based declaration at top level
|
|
81
|
+
const emit = defineEmits<{
|
|
82
|
+
submit: [data: FormData]
|
|
83
|
+
cancel: []
|
|
84
|
+
'update:modelValue': [value: string]
|
|
85
|
+
}>()
|
|
86
|
+
|
|
87
|
+
function handleSubmit(data: FormData) {
|
|
88
|
+
emit('submit', data)
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Correct - Using constant arrays (compile-time constant):**
|
|
94
|
+
```vue
|
|
95
|
+
<script setup>
|
|
96
|
+
// CORRECT: Literal array is fine
|
|
97
|
+
const emit = defineEmits(['submit', 'cancel'])
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Why This Restriction Exists
|
|
102
|
+
|
|
103
|
+
Vue's compiler processes `<script setup>` macros at compile time, not runtime. The arguments must be statically analyzable so Vue can:
|
|
104
|
+
|
|
105
|
+
1. Generate the correct component options
|
|
106
|
+
2. Provide TypeScript type inference
|
|
107
|
+
3. Enable IDE support for event autocompletion
|
|
108
|
+
4. Validate emitted events
|
|
109
|
+
|
|
110
|
+
Since the macro is hoisted out of `<script setup>` during compilation, it cannot access anything that only exists at runtime.
|
|
111
|
+
|
|
112
|
+
## Using emit in Composables
|
|
113
|
+
|
|
114
|
+
If you want to share emit logic in a composable, pass the emit function as an argument:
|
|
115
|
+
|
|
116
|
+
**Correct - Pass emit to composable:**
|
|
117
|
+
```vue
|
|
118
|
+
<script setup>
|
|
119
|
+
const emit = defineEmits(['submit', 'cancel', 'validate'])
|
|
120
|
+
|
|
121
|
+
// Pass emit to composable
|
|
122
|
+
const { handleSubmit, handleCancel } = useFormEvents(emit)
|
|
123
|
+
</script>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
// composables/useFormEvents.js
|
|
128
|
+
export function useFormEvents(emit) {
|
|
129
|
+
function handleSubmit(data) {
|
|
130
|
+
emit('submit', data)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleCancel() {
|
|
134
|
+
emit('cancel')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { handleSubmit, handleCancel }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## ESLint Rule
|
|
142
|
+
|
|
143
|
+
The `eslint-plugin-vue` provides the `vue/valid-define-emits` rule that catches these errors:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
// eslint.config.js
|
|
147
|
+
export default [
|
|
148
|
+
{
|
|
149
|
+
rules: {
|
|
150
|
+
'vue/valid-define-emits': 'error'
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This rule reports:
|
|
157
|
+
- `defineEmits` used inside functions
|
|
158
|
+
- `defineEmits` referencing local variables
|
|
159
|
+
- Multiple `defineEmits` calls in the same component
|
|
160
|
+
- `defineEmits` used outside `<script setup>`
|
|
161
|
+
|
|
162
|
+
## Reference
|
|
163
|
+
- [Vue.js SFC script setup](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
|
|
164
|
+
- [ESLint vue/valid-define-emits](https://eslint.vuejs.org/rules/valid-define-emits)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cannot Mix Runtime and Type Declarations in defineEmits
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Using both array/object syntax AND TypeScript generics in defineEmits causes compile errors
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, defineEmits, typescript, compilation-error, script-setup]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Cannot Mix Runtime and Type Declarations in defineEmits
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - `defineEmits` supports two declaration styles: runtime (array/object syntax) and type-based (TypeScript generics). You CANNOT use both at the same time. Attempting to do so results in a compile-time error.
|
|
12
|
+
|
|
13
|
+
This is a common mistake when learning Vue 3 with TypeScript.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Choose ONE declaration style: runtime OR type-based
|
|
18
|
+
- [ ] For TypeScript projects, prefer type-based declaration
|
|
19
|
+
- [ ] For JavaScript projects, use runtime (array/object) declaration
|
|
20
|
+
- [ ] Never pass arguments when using generic type parameter
|
|
21
|
+
|
|
22
|
+
## The Problem
|
|
23
|
+
|
|
24
|
+
**Incorrect - Mixing both styles:**
|
|
25
|
+
```vue
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
// ERROR: Cannot use both type argument and runtime argument
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
submit: [data: FormData]
|
|
30
|
+
}>(['submit']) // This array argument causes the error!
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Compiler error:**
|
|
35
|
+
```
|
|
36
|
+
defineEmits() cannot accept both type and non-type arguments at the same time.
|
|
37
|
+
Use one or the other.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Also incorrect:**
|
|
41
|
+
```vue
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
// ERROR: Same problem with object syntax
|
|
44
|
+
const emit = defineEmits<{
|
|
45
|
+
submit: [data: FormData]
|
|
46
|
+
}>({
|
|
47
|
+
submit: (data) => !!data
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Correct: Type-Based Declaration (TypeScript)
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
// CORRECT: Type argument only, no runtime argument
|
|
57
|
+
const emit = defineEmits<{
|
|
58
|
+
submit: [data: FormData]
|
|
59
|
+
cancel: []
|
|
60
|
+
'update:modelValue': [value: string]
|
|
61
|
+
}>()
|
|
62
|
+
|
|
63
|
+
emit('submit', formData) // TypeScript validates this
|
|
64
|
+
emit('cancel')
|
|
65
|
+
emit('unknown') // TypeScript error: unknown event
|
|
66
|
+
</script>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Alternative call signature syntax:**
|
|
70
|
+
```vue
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
const emit = defineEmits<{
|
|
73
|
+
(e: 'submit', data: FormData): void
|
|
74
|
+
(e: 'cancel'): void
|
|
75
|
+
(e: 'update:modelValue', value: string): void
|
|
76
|
+
}>()
|
|
77
|
+
</script>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Correct: Runtime Declaration (JavaScript or Simple Cases)
|
|
81
|
+
|
|
82
|
+
**Array syntax:**
|
|
83
|
+
```vue
|
|
84
|
+
<script setup>
|
|
85
|
+
// CORRECT: Runtime array, no type argument
|
|
86
|
+
const emit = defineEmits(['submit', 'cancel', 'update:modelValue'])
|
|
87
|
+
|
|
88
|
+
emit('submit', formData)
|
|
89
|
+
emit('cancel')
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Object syntax with validation:**
|
|
94
|
+
```vue
|
|
95
|
+
<script setup>
|
|
96
|
+
// CORRECT: Runtime object for validation
|
|
97
|
+
const emit = defineEmits({
|
|
98
|
+
submit: (data) => {
|
|
99
|
+
if (!data?.email) {
|
|
100
|
+
console.warn('Missing email')
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
return true
|
|
104
|
+
},
|
|
105
|
+
cancel: null // No validation
|
|
106
|
+
})
|
|
107
|
+
</script>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Adding Validation to Type-Based Emits
|
|
111
|
+
|
|
112
|
+
If you want TypeScript types AND runtime validation, define the validator separately:
|
|
113
|
+
|
|
114
|
+
```vue
|
|
115
|
+
<script setup lang="ts">
|
|
116
|
+
interface FormData {
|
|
117
|
+
email: string
|
|
118
|
+
message: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Type-based declaration for TypeScript
|
|
122
|
+
const emit = defineEmits<{
|
|
123
|
+
submit: [data: FormData]
|
|
124
|
+
}>()
|
|
125
|
+
|
|
126
|
+
// Separate validation function
|
|
127
|
+
function emitSubmit(data: FormData) {
|
|
128
|
+
if (!data.email.includes('@')) {
|
|
129
|
+
console.warn('Invalid email format')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
emit('submit', data)
|
|
133
|
+
}
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<template>
|
|
137
|
+
<button @click="emitSubmit(formData)">Submit</button>
|
|
138
|
+
</template>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Choosing Between Styles
|
|
142
|
+
|
|
143
|
+
| Style | Use When | Benefits |
|
|
144
|
+
|-------|----------|----------|
|
|
145
|
+
| Type-based | TypeScript project | Compile-time checking, IDE support |
|
|
146
|
+
| Array | JavaScript, simple events | Simple, no types needed |
|
|
147
|
+
| Object | Need runtime validation | Validates payloads at runtime |
|
|
148
|
+
|
|
149
|
+
**Recommendation:** In TypeScript projects, use type-based declaration. It provides the best developer experience with autocompletion and type checking.
|
|
150
|
+
|
|
151
|
+
## Same Rule Applies to defineProps
|
|
152
|
+
|
|
153
|
+
This restriction also applies to `defineProps`:
|
|
154
|
+
|
|
155
|
+
```vue
|
|
156
|
+
<script setup lang="ts">
|
|
157
|
+
// ERROR: Cannot mix
|
|
158
|
+
const props = defineProps<{ name: string }>({ name: String })
|
|
159
|
+
|
|
160
|
+
// CORRECT: Type-based only
|
|
161
|
+
const props = defineProps<{ name: string }>()
|
|
162
|
+
|
|
163
|
+
// CORRECT: Runtime only
|
|
164
|
+
const props = defineProps({ name: String })
|
|
165
|
+
</script>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Reference
|
|
169
|
+
- [Vue.js SFC script setup - defineEmits](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
|
|
170
|
+
- [Vue.js TypeScript with Composition API](https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineModel Object Properties Must Be Replaced, Not Mutated
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Mutating object properties via defineModel doesn't emit update events, breaking parent sync
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, v-model, defineModel, objects, reactivity, two-way-binding]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# defineModel Object Properties Must Be Replaced, Not Mutated
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When using `defineModel()` with objects or arrays, directly mutating nested properties like `model.value.prop = x` does NOT emit the `update:modelValue` event. The parent component never receives the change notification, causing silent sync failures.
|
|
12
|
+
|
|
13
|
+
This happens because Vue only detects when the `model.value` reference itself changes, not when properties of the object are mutated in place.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never mutate object properties directly: `model.value.prop = x`
|
|
18
|
+
- [ ] Always create a new object reference when updating: `model.value = {...model.value, prop: x}`
|
|
19
|
+
- [ ] For arrays, use spread or slice: `model.value = [...model.value, newItem]`
|
|
20
|
+
- [ ] Consider using structuredClone for deeply nested objects
|
|
21
|
+
|
|
22
|
+
**Incorrect - Mutation without event emission:**
|
|
23
|
+
```vue
|
|
24
|
+
<script setup>
|
|
25
|
+
// Child component with object v-model
|
|
26
|
+
const model = defineModel<{ name: string; age: number }>()
|
|
27
|
+
|
|
28
|
+
function updateName(newName: string) {
|
|
29
|
+
// WRONG: This mutates the object in place
|
|
30
|
+
// Parent receives NO update:modelValue event!
|
|
31
|
+
model.value.name = newName
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addToList() {
|
|
35
|
+
// WRONG: Push mutates the array
|
|
36
|
+
model.value.items.push('new item') // Parent not notified
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Correct - Replace object reference to trigger event:**
|
|
42
|
+
```vue
|
|
43
|
+
<script setup>
|
|
44
|
+
const model = defineModel<{ name: string; age: number }>()
|
|
45
|
+
|
|
46
|
+
function updateName(newName: string) {
|
|
47
|
+
// CORRECT: Create new object reference
|
|
48
|
+
// This triggers update:modelValue event to parent
|
|
49
|
+
model.value = {
|
|
50
|
+
...model.value,
|
|
51
|
+
name: newName
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function addToList() {
|
|
56
|
+
// CORRECT: Create new array reference
|
|
57
|
+
model.value = {
|
|
58
|
+
...model.value,
|
|
59
|
+
items: [...model.value.items, 'new item']
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Deep Nesting Requires Full Path Replacement
|
|
66
|
+
|
|
67
|
+
```vue
|
|
68
|
+
<script setup>
|
|
69
|
+
const model = defineModel<{
|
|
70
|
+
user: {
|
|
71
|
+
address: {
|
|
72
|
+
city: string
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}>()
|
|
76
|
+
|
|
77
|
+
// WRONG: Deep mutation
|
|
78
|
+
model.value.user.address.city = 'New York'
|
|
79
|
+
|
|
80
|
+
// CORRECT: Replace entire chain
|
|
81
|
+
model.value = {
|
|
82
|
+
...model.value,
|
|
83
|
+
user: {
|
|
84
|
+
...model.value.user,
|
|
85
|
+
address: {
|
|
86
|
+
...model.value.user.address,
|
|
87
|
+
city: 'New York'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ALTERNATIVE: Use structuredClone for complex updates
|
|
93
|
+
function updateCity(city: string) {
|
|
94
|
+
const updated = structuredClone(model.value)
|
|
95
|
+
updated.user.address.city = city
|
|
96
|
+
model.value = updated // New reference triggers event
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Race Condition Warning with Spread Operator
|
|
102
|
+
|
|
103
|
+
When multiple updates occur rapidly, earlier changes can be lost:
|
|
104
|
+
|
|
105
|
+
```vue
|
|
106
|
+
<script setup>
|
|
107
|
+
const model = defineModel<{ a: string; b: string }>()
|
|
108
|
+
|
|
109
|
+
// CAUTION: Race condition if called in same tick
|
|
110
|
+
function updateBothWrong() {
|
|
111
|
+
model.value = { ...model.value, a: 'new-a' } // First update
|
|
112
|
+
model.value = { ...model.value, b: 'new-b' } // May use stale model.value!
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// CORRECT: Batch updates into single assignment
|
|
116
|
+
function updateBothCorrect() {
|
|
117
|
+
model.value = {
|
|
118
|
+
...model.value,
|
|
119
|
+
a: 'new-a',
|
|
120
|
+
b: 'new-b'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Alternative: VueUse's useVModel with Deep Option
|
|
127
|
+
|
|
128
|
+
For complex objects, consider VueUse:
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<script setup>
|
|
132
|
+
import { useVModel } from '@vueuse/core'
|
|
133
|
+
|
|
134
|
+
const props = defineProps<{ modelValue: { name: string } }>()
|
|
135
|
+
const emit = defineEmits(['update:modelValue'])
|
|
136
|
+
|
|
137
|
+
// Deep tracking with passive updates
|
|
138
|
+
const model = useVModel(props, 'modelValue', emit, { deep: true, passive: true })
|
|
139
|
+
|
|
140
|
+
// Now direct mutations work
|
|
141
|
+
model.value.name = 'New Name' // Properly syncs with parent
|
|
142
|
+
</script>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Reference
|
|
146
|
+
- [Vue.js Component v-model](https://vuejs.org/guide/components/v-model.html)
|
|
147
|
+
- [GitHub Discussion: defineModel with objects](https://github.com/orgs/vuejs/discussions/10538)
|
|
148
|
+
- [SIMPL Engineering: Vue defineModel Pitfalls](https://engineering.simpl.de/post/vue_definemodel/)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use nextTick() to Wait for DOM Updates
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: DOM updates are batched and asynchronous - direct DOM access after state changes sees stale values
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, dom, nextTick, reactivity, async]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Use nextTick() to Wait for DOM Updates
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue batches DOM updates asynchronously for performance. If you access the DOM immediately after changing reactive state, you'll see the old values. Use `nextTick()` to wait for the DOM to update.
|
|
12
|
+
|
|
13
|
+
When you modify reactive state, Vue doesn't update the DOM synchronously. Instead, it buffers changes and applies them in the next "tick" of the event loop. This is a performance optimization, but it can cause bugs when you need to read from or manipulate the DOM after state changes.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use `await nextTick()` when you need to access updated DOM elements after state changes
|
|
18
|
+
- [ ] Use `nextTick()` when measuring DOM elements (heights, widths) after data changes
|
|
19
|
+
- [ ] Use `nextTick()` when focusing inputs or scrolling after content updates
|
|
20
|
+
- [ ] Consider if you really need DOM access - often you can work with reactive data instead
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref } from 'vue'
|
|
25
|
+
|
|
26
|
+
const message = ref('Hello')
|
|
27
|
+
const messageEl = ref(null)
|
|
28
|
+
|
|
29
|
+
function updateMessage() {
|
|
30
|
+
message.value = 'Updated!'
|
|
31
|
+
|
|
32
|
+
// WRONG: DOM still shows "Hello" at this point
|
|
33
|
+
console.log(messageEl.value.textContent) // "Hello" - stale!
|
|
34
|
+
|
|
35
|
+
// WRONG: Scrolling/focusing may not work correctly
|
|
36
|
+
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Correct:**
|
|
41
|
+
```javascript
|
|
42
|
+
import { ref, nextTick } from 'vue'
|
|
43
|
+
|
|
44
|
+
const message = ref('Hello')
|
|
45
|
+
const messageEl = ref(null)
|
|
46
|
+
|
|
47
|
+
async function updateMessage() {
|
|
48
|
+
message.value = 'Updated!'
|
|
49
|
+
|
|
50
|
+
// CORRECT: Wait for DOM to update
|
|
51
|
+
await nextTick()
|
|
52
|
+
|
|
53
|
+
// Now the DOM is updated
|
|
54
|
+
console.log(messageEl.value.textContent) // "Updated!"
|
|
55
|
+
|
|
56
|
+
// Scrolling and focusing now work correctly
|
|
57
|
+
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Alternative: callback syntax
|
|
61
|
+
function updateWithCallback() {
|
|
62
|
+
message.value = 'Updated!'
|
|
63
|
+
|
|
64
|
+
nextTick(() => {
|
|
65
|
+
console.log(messageEl.value.textContent) // "Updated!"
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<script setup>
|
|
72
|
+
import { ref, nextTick } from 'vue'
|
|
73
|
+
|
|
74
|
+
const items = ref([])
|
|
75
|
+
const listRef = ref(null)
|
|
76
|
+
|
|
77
|
+
async function addItem() {
|
|
78
|
+
items.value.push({ id: Date.now(), text: 'New item' })
|
|
79
|
+
|
|
80
|
+
await nextTick()
|
|
81
|
+
|
|
82
|
+
// Now we can safely scroll to the new item
|
|
83
|
+
listRef.value.lastElementChild?.scrollIntoView({ behavior: 'smooth' })
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Reference
|
|
89
|
+
- [Vue.js Reactivity Fundamentals - DOM Update Timing](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#dom-update-timing)
|
|
90
|
+
- [Vue.js nextTick API](https://vuejs.org/api/general.html#nexttick)
|