@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,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Provide Must Be Called Synchronously During Setup
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Calling provide() asynchronously or conditionally may fail silently or cause inconsistent injection behavior
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, provide-inject, composition-api, async, setup]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Provide Must Be Called Synchronously During Setup
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - The `provide()` function must be called synchronously during the component's `setup()` phase. Calling it asynchronously (inside callbacks, promises, or after await) will fail silently, and descendant components will not receive the provided value.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Always call `provide()` at the top level of `setup()` or `<script setup>`
|
|
16
|
+
- [ ] Never call `provide()` inside async callbacks or after await statements
|
|
17
|
+
- [ ] For async data, provide a ref first, then update its value later
|
|
18
|
+
- [ ] Use immediate `provide()` with reactive containers for dynamic data
|
|
19
|
+
|
|
20
|
+
## The Gotcha: Async Provide Fails Silently
|
|
21
|
+
|
|
22
|
+
**Wrong - Provide after async operation:**
|
|
23
|
+
```vue
|
|
24
|
+
<script setup>
|
|
25
|
+
import { provide } from 'vue'
|
|
26
|
+
|
|
27
|
+
// WRONG: provide() called after await - will NOT work
|
|
28
|
+
onMounted(async () => {
|
|
29
|
+
const userData = await fetchUser()
|
|
30
|
+
provide('user', userData) // Silent failure!
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Wrong - Provide inside callback:**
|
|
36
|
+
```vue
|
|
37
|
+
<script setup>
|
|
38
|
+
import { provide } from 'vue'
|
|
39
|
+
|
|
40
|
+
// WRONG: provide() inside callback - will NOT work
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
provide('config', { theme: 'dark' }) // Silent failure!
|
|
43
|
+
}, 0)
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Wrong - Provide after await in setup:**
|
|
48
|
+
```vue
|
|
49
|
+
<script setup>
|
|
50
|
+
import { provide } from 'vue'
|
|
51
|
+
|
|
52
|
+
const response = await fetch('/api/config')
|
|
53
|
+
const config = await response.json()
|
|
54
|
+
|
|
55
|
+
// WRONG: This is after an await, setup context may be lost
|
|
56
|
+
provide('config', config) // May not work reliably
|
|
57
|
+
</script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Solution: Provide Synchronously, Update Async
|
|
61
|
+
|
|
62
|
+
**Correct - Provide ref immediately, update later:**
|
|
63
|
+
```vue
|
|
64
|
+
<script setup>
|
|
65
|
+
import { provide, ref, onMounted } from 'vue'
|
|
66
|
+
|
|
67
|
+
// Provide immediately with initial value
|
|
68
|
+
const user = ref(null)
|
|
69
|
+
const isLoading = ref(true)
|
|
70
|
+
const error = ref(null)
|
|
71
|
+
|
|
72
|
+
provide('userState', {
|
|
73
|
+
user,
|
|
74
|
+
isLoading,
|
|
75
|
+
error
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Update the ref values asynchronously
|
|
79
|
+
onMounted(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const userData = await fetchUser()
|
|
82
|
+
user.value = userData
|
|
83
|
+
} catch (e) {
|
|
84
|
+
error.value = e
|
|
85
|
+
} finally {
|
|
86
|
+
isLoading.value = false
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<!-- Consumer component -->
|
|
94
|
+
<script setup>
|
|
95
|
+
import { inject } from 'vue'
|
|
96
|
+
|
|
97
|
+
const { user, isLoading, error } = inject('userState')
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<div v-if="isLoading">Loading...</div>
|
|
102
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
103
|
+
<div v-else>Welcome, {{ user?.name }}</div>
|
|
104
|
+
</template>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Pattern: Async Data Provider
|
|
108
|
+
|
|
109
|
+
Create a reusable pattern for async-provided data:
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<!-- AsyncDataProvider.vue -->
|
|
113
|
+
<script setup>
|
|
114
|
+
import { provide, ref, onMounted, watch } from 'vue'
|
|
115
|
+
|
|
116
|
+
const props = defineProps({
|
|
117
|
+
fetchFn: {
|
|
118
|
+
type: Function,
|
|
119
|
+
required: true
|
|
120
|
+
},
|
|
121
|
+
provideKey: {
|
|
122
|
+
type: [String, Symbol],
|
|
123
|
+
required: true
|
|
124
|
+
},
|
|
125
|
+
immediate: {
|
|
126
|
+
type: Boolean,
|
|
127
|
+
default: true
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const data = ref(null)
|
|
132
|
+
const isLoading = ref(false)
|
|
133
|
+
const error = ref(null)
|
|
134
|
+
|
|
135
|
+
async function load() {
|
|
136
|
+
isLoading.value = true
|
|
137
|
+
error.value = null
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
data.value = await props.fetchFn()
|
|
141
|
+
} catch (e) {
|
|
142
|
+
error.value = e
|
|
143
|
+
} finally {
|
|
144
|
+
isLoading.value = false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Provide synchronously
|
|
149
|
+
provide(props.provideKey, {
|
|
150
|
+
data,
|
|
151
|
+
isLoading,
|
|
152
|
+
error,
|
|
153
|
+
reload: load
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Fetch asynchronously
|
|
157
|
+
if (props.immediate) {
|
|
158
|
+
onMounted(load)
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<template>
|
|
163
|
+
<slot />
|
|
164
|
+
</template>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Usage:
|
|
168
|
+
|
|
169
|
+
```vue
|
|
170
|
+
<template>
|
|
171
|
+
<AsyncDataProvider
|
|
172
|
+
:fetch-fn="() => api.getUser(userId)"
|
|
173
|
+
provide-key="userData"
|
|
174
|
+
>
|
|
175
|
+
<UserProfile />
|
|
176
|
+
</AsyncDataProvider>
|
|
177
|
+
</template>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Why This Happens
|
|
181
|
+
|
|
182
|
+
Vue's `provide()` relies on the current component instance context, which is only available synchronously during setup. After setup completes:
|
|
183
|
+
|
|
184
|
+
1. The setup context is cleared
|
|
185
|
+
2. `provide()` can't find the current instance
|
|
186
|
+
3. The call fails silently (no error thrown)
|
|
187
|
+
|
|
188
|
+
## Checking for Setup Context
|
|
189
|
+
|
|
190
|
+
You can verify if setup context is available:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
import { getCurrentInstance } from 'vue'
|
|
194
|
+
|
|
195
|
+
function debugProvide(key, value) {
|
|
196
|
+
const instance = getCurrentInstance()
|
|
197
|
+
|
|
198
|
+
if (!instance) {
|
|
199
|
+
console.error(
|
|
200
|
+
`provide() called outside setup context. ` +
|
|
201
|
+
`Key: ${String(key)}. This will fail silently.`
|
|
202
|
+
)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
provide(key, value)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## App-Level Provide (Exception)
|
|
211
|
+
|
|
212
|
+
`app.provide()` can be called anytime during app initialization:
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
// main.js
|
|
216
|
+
import { createApp } from 'vue'
|
|
217
|
+
import App from './App.vue'
|
|
218
|
+
|
|
219
|
+
const app = createApp(App)
|
|
220
|
+
|
|
221
|
+
// This works - app-level provide
|
|
222
|
+
app.provide('appConfig', { version: '1.0.0' })
|
|
223
|
+
|
|
224
|
+
// Even async is OK at app level before mount
|
|
225
|
+
fetchConfig().then(config => {
|
|
226
|
+
app.provide('apiConfig', config)
|
|
227
|
+
app.mount('#app')
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
But once the app is mounted, `app.provide()` should not be called.
|
|
232
|
+
|
|
233
|
+
## Reference
|
|
234
|
+
- [Vue.js Composition API - provide()](https://vuejs.org/api/composition-api-dependency-injection.html#provide)
|
|
235
|
+
- [Vue.js Provide/Inject Guide](https://vuejs.org/guide/components/provide-inject.html)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Never Destructure reactive() Objects Directly
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Destructuring reactive objects breaks reactivity - changes won't trigger updates
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, reactivity, reactive, composition-api, destructuring]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Never Destructure reactive() Objects Directly
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Destructuring a `reactive()` object breaks the reactive connection. Updates to destructured variables won't trigger UI updates, leading to stale data display.
|
|
12
|
+
|
|
13
|
+
Vue's `reactive()` uses JavaScript Proxies to track property access. When you destructure, you extract primitive values from the proxy, losing the reactive connection. This is especially dangerous when destructuring from composables or imported state.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never destructure reactive objects directly if you need reactivity
|
|
18
|
+
- [ ] Use `toRefs()` to convert reactive object properties to refs before destructuring
|
|
19
|
+
- [ ] Consider using `ref()` instead of `reactive()` to avoid this pitfall entirely
|
|
20
|
+
- [ ] When importing state from composables, check if it's reactive before destructuring
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { reactive } from 'vue'
|
|
25
|
+
|
|
26
|
+
const state = reactive({
|
|
27
|
+
count: 0,
|
|
28
|
+
name: 'Vue'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// WRONG: Destructuring breaks reactivity
|
|
32
|
+
const { count, name } = state
|
|
33
|
+
|
|
34
|
+
// These updates work on the original state...
|
|
35
|
+
state.count++ // state.count is now 1
|
|
36
|
+
|
|
37
|
+
// ...but the destructured variables are NOT updated
|
|
38
|
+
console.log(count) // Still 0! Lost reactivity
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// WRONG: Destructuring from a composable
|
|
43
|
+
function useCounter() {
|
|
44
|
+
const state = reactive({ count: 0 })
|
|
45
|
+
return state
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { count } = useCounter() // count is now a non-reactive primitive
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct:**
|
|
52
|
+
```javascript
|
|
53
|
+
import { reactive, toRefs } from 'vue'
|
|
54
|
+
|
|
55
|
+
const state = reactive({
|
|
56
|
+
count: 0,
|
|
57
|
+
name: 'Vue'
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// CORRECT: Use toRefs() to maintain reactivity
|
|
61
|
+
const { count, name } = toRefs(state)
|
|
62
|
+
|
|
63
|
+
state.count++
|
|
64
|
+
console.log(count.value) // 1 - Reactivity preserved! (note: now needs .value)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
// CORRECT: Return toRefs from composables
|
|
69
|
+
function useCounter() {
|
|
70
|
+
const state = reactive({ count: 0 })
|
|
71
|
+
return toRefs(state) // Now safe to destructure
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { count } = useCounter() // count is now a ref, reactivity preserved
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// ALTERNATIVE: Just use ref() to avoid the issue entirely
|
|
79
|
+
import { ref } from 'vue'
|
|
80
|
+
|
|
81
|
+
const count = ref(0)
|
|
82
|
+
const name = ref('Vue')
|
|
83
|
+
|
|
84
|
+
// No destructuring needed, no gotchas
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Reference
|
|
88
|
+
- [Vue.js Reactivity Fundamentals - reactive()](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive)
|
|
89
|
+
- [Vue.js Reactivity API - toRefs()](https://vuejs.org/api/reactivity-utilities.html#torefs)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Debug Hooks to Trace Reactivity Issues
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Debug hooks help identify which dependencies trigger re-renders and watcher executions
|
|
5
|
+
type: efficiency
|
|
6
|
+
tags: [vue3, reactivity, debugging, computed, watch, development]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Use Debug Hooks to Trace Reactivity Issues
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue provides debug hooks (`onTrack`, `onTrigger`, `renderTracked`, `renderTriggered`) that help identify exactly which reactive dependencies are being tracked and which mutations trigger re-execution. These are invaluable for debugging performance issues and unexpected re-renders.
|
|
12
|
+
|
|
13
|
+
Debug hooks only work in development mode and are stripped in production builds. Use them to understand why a computed property, watcher, or component is re-executing.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use `onTrack` and `onTrigger` options on computed/watch for granular debugging
|
|
18
|
+
- [ ] Use `onRenderTracked` and `onRenderTriggered` lifecycle hooks for component render debugging
|
|
19
|
+
- [ ] Add `debugger` statements inside hooks to pause execution and inspect state
|
|
20
|
+
- [ ] Remove or comment out debug hooks before production (they're no-ops but add clutter)
|
|
21
|
+
|
|
22
|
+
> **Note:** `onTrack` and `onTrigger` are development-only hooks. They are stripped from production builds and may not fire in test environments (e.g., Vitest, Jest) depending on how Vue is bundled. If you need to verify reactivity behavior in tests, use direct assertions on reactive state changes rather than relying on these debug callbacks.
|
|
23
|
+
|
|
24
|
+
**Debugging computed properties:**
|
|
25
|
+
```javascript
|
|
26
|
+
import { ref, computed } from 'vue'
|
|
27
|
+
|
|
28
|
+
const count = ref(0)
|
|
29
|
+
const doubled = computed(() => count.value * 2, {
|
|
30
|
+
onTrack(event) {
|
|
31
|
+
// Called when a dependency is tracked
|
|
32
|
+
// event.target = the reactive object
|
|
33
|
+
// event.key = the property being accessed
|
|
34
|
+
debugger
|
|
35
|
+
console.log('Tracking:', event)
|
|
36
|
+
},
|
|
37
|
+
onTrigger(event) {
|
|
38
|
+
// Called when a dependency mutation triggers re-computation
|
|
39
|
+
debugger
|
|
40
|
+
console.log('Triggered by:', event)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Debugging watchers:**
|
|
46
|
+
```javascript
|
|
47
|
+
import { ref, watch, watchEffect } from 'vue'
|
|
48
|
+
|
|
49
|
+
const source = ref(0)
|
|
50
|
+
|
|
51
|
+
// With watch()
|
|
52
|
+
watch(source, (newVal, oldVal) => {
|
|
53
|
+
console.log('Changed:', oldVal, '->', newVal)
|
|
54
|
+
}, {
|
|
55
|
+
onTrack(e) {
|
|
56
|
+
debugger // Pause to see what's being tracked
|
|
57
|
+
},
|
|
58
|
+
onTrigger(e) {
|
|
59
|
+
debugger // Pause to see what triggered the watcher
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// With watchEffect()
|
|
64
|
+
watchEffect(() => {
|
|
65
|
+
console.log('Source is:', source.value)
|
|
66
|
+
}, {
|
|
67
|
+
onTrack(e) {
|
|
68
|
+
console.log('Tracking dependency:', e.key)
|
|
69
|
+
},
|
|
70
|
+
onTrigger(e) {
|
|
71
|
+
console.log('Triggered by:', e.key, 'mutation')
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Debugging component renders:**
|
|
77
|
+
```vue
|
|
78
|
+
<script setup>
|
|
79
|
+
import { onRenderTracked, onRenderTriggered, ref } from 'vue'
|
|
80
|
+
|
|
81
|
+
const count = ref(0)
|
|
82
|
+
|
|
83
|
+
// Called for every reactive dependency accessed during render
|
|
84
|
+
onRenderTracked((event) => {
|
|
85
|
+
console.log('Render tracked:', event.key, 'from', event.target)
|
|
86
|
+
debugger // Pause to inspect which dependencies are tracked
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Called when a reactive dependency triggers re-render
|
|
90
|
+
onRenderTriggered((event) => {
|
|
91
|
+
console.log('Render triggered by:', event.key)
|
|
92
|
+
console.log('Old value:', event.oldValue)
|
|
93
|
+
console.log('New value:', event.newValue)
|
|
94
|
+
debugger // Pause to see exactly what caused the re-render
|
|
95
|
+
})
|
|
96
|
+
</script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Options API equivalent:**
|
|
100
|
+
```javascript
|
|
101
|
+
export default {
|
|
102
|
+
data() {
|
|
103
|
+
return { count: 0 }
|
|
104
|
+
},
|
|
105
|
+
renderTracked(event) {
|
|
106
|
+
console.log('Dependency tracked during render:', event)
|
|
107
|
+
debugger
|
|
108
|
+
},
|
|
109
|
+
renderTriggered(event) {
|
|
110
|
+
console.log('Re-render triggered by:', event)
|
|
111
|
+
debugger
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Debug event properties:**
|
|
117
|
+
```javascript
|
|
118
|
+
// The event object contains:
|
|
119
|
+
{
|
|
120
|
+
effect: ReactiveEffect, // The effect being debugged
|
|
121
|
+
target: object, // The reactive object
|
|
122
|
+
type: 'get' | 'set' | 'add' | 'delete' | 'clear',
|
|
123
|
+
key: string | symbol, // The property being accessed/mutated
|
|
124
|
+
oldValue: any, // Previous value (for onTrigger)
|
|
125
|
+
newValue: any // New value (for onTrigger)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Reference
|
|
130
|
+
- [Vue.js Reactivity in Depth - Debugging](https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging)
|
|
131
|
+
- [Vue.js computed() API](https://vuejs.org/api/reactivity-core.html#computed)
|
|
132
|
+
- [Vue.js onRenderTracked()](https://vuejs.org/api/composition-api-lifecycle.html#onrendertracked)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use markRaw() for Objects That Should Never Be Reactive
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Library instances, DOM nodes, and complex objects cause overhead and bugs when wrapped in Vue proxies
|
|
5
|
+
type: efficiency
|
|
6
|
+
tags: [vue3, reactivity, markRaw, performance, external-libraries, dom]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Use markRaw() for Objects That Should Never Be Reactive
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue's `markRaw()` tells the reactivity system to never wrap an object in a Proxy. Use it for library instances, DOM nodes, class instances with internal state, and complex objects that Vue shouldn't track. This prevents unnecessary proxy overhead and avoids subtle bugs from double-proxying.
|
|
12
|
+
|
|
13
|
+
Without `markRaw()`, placing these objects inside reactive state causes Vue to wrap them in Proxies, which can break library internals, cause identity issues, and waste memory on objects that don't need change tracking.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use `markRaw()` for third-party library instances (maps, charts, editors)
|
|
18
|
+
- [ ] Use `markRaw()` for DOM elements stored in reactive state
|
|
19
|
+
- [ ] Use `markRaw()` for class instances that manage their own state
|
|
20
|
+
- [ ] Use `markRaw()` for large static data that will never change
|
|
21
|
+
- [ ] Remember: markRaw only affects the root level - nested objects may still be proxied
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```javascript
|
|
25
|
+
import { reactive, ref } from 'vue'
|
|
26
|
+
import mapboxgl from 'mapbox-gl'
|
|
27
|
+
import * as monaco from 'monaco-editor'
|
|
28
|
+
|
|
29
|
+
// WRONG: Library instances wrapped in Proxy
|
|
30
|
+
const state = reactive({
|
|
31
|
+
map: new mapboxgl.Map({ container: 'map' }), // Proxied!
|
|
32
|
+
editor: monaco.editor.create(element, {}), // Proxied!
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Problems:
|
|
36
|
+
// 1. Library's internal this references may break
|
|
37
|
+
// 2. Unnecessary memory overhead
|
|
38
|
+
// 3. Methods may not work correctly through proxy
|
|
39
|
+
// 4. Performance degradation
|
|
40
|
+
|
|
41
|
+
// WRONG: DOM elements in reactive state
|
|
42
|
+
const elements = reactive({
|
|
43
|
+
container: document.getElementById('app'), // Proxied DOM node!
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Correct:**
|
|
48
|
+
```javascript
|
|
49
|
+
import { reactive, markRaw, shallowRef } from 'vue'
|
|
50
|
+
import mapboxgl from 'mapbox-gl'
|
|
51
|
+
import * as monaco from 'monaco-editor'
|
|
52
|
+
|
|
53
|
+
// CORRECT: Mark library instances as raw
|
|
54
|
+
const state = reactive({
|
|
55
|
+
map: markRaw(new mapboxgl.Map({ container: 'map' })),
|
|
56
|
+
editor: markRaw(monaco.editor.create(element, {})),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// CORRECT: Or use shallowRef for mutable references
|
|
60
|
+
const map = shallowRef(null)
|
|
61
|
+
onMounted(() => {
|
|
62
|
+
map.value = markRaw(new mapboxgl.Map({ container: 'map' }))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// CORRECT: Large static data
|
|
66
|
+
const geoJsonData = markRaw(await fetch('/huge-geojson.json').then(r => r.json()))
|
|
67
|
+
const state = reactive({
|
|
68
|
+
mapData: geoJsonData // Won't be proxied
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Class instances with internal state:**
|
|
73
|
+
```javascript
|
|
74
|
+
import { markRaw, reactive } from 'vue'
|
|
75
|
+
|
|
76
|
+
class WebSocketManager {
|
|
77
|
+
constructor(url) {
|
|
78
|
+
this.socket = new WebSocket(url)
|
|
79
|
+
this.listeners = new Map()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
on(event, callback) {
|
|
83
|
+
this.listeners.set(event, callback)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// CORRECT: Mark class instance
|
|
88
|
+
const wsManager = markRaw(new WebSocketManager('ws://example.com'))
|
|
89
|
+
|
|
90
|
+
const state = reactive({
|
|
91
|
+
connection: wsManager // Won't be proxied
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Can still use the instance normally
|
|
95
|
+
state.connection.on('message', handleMessage)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Gotcha: markRaw only affects root level:**
|
|
99
|
+
```javascript
|
|
100
|
+
import { markRaw, reactive } from 'vue'
|
|
101
|
+
|
|
102
|
+
const rawObject = markRaw({
|
|
103
|
+
nested: { value: 1 } // This nested object is NOT marked raw
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const state = reactive({
|
|
107
|
+
data: rawObject
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// rawObject itself won't be proxied
|
|
111
|
+
// But if you access nested objects through a reactive parent:
|
|
112
|
+
const container = reactive({ raw: rawObject })
|
|
113
|
+
// container.raw.nested might still be proxied in some cases
|
|
114
|
+
|
|
115
|
+
// SAFER: Use shallowRef for the container
|
|
116
|
+
import { shallowRef } from 'vue'
|
|
117
|
+
const safeContainer = shallowRef(rawObject)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Combining with shallowRef for best results:**
|
|
121
|
+
```javascript
|
|
122
|
+
import { shallowRef, markRaw, onMounted, onUnmounted } from 'vue'
|
|
123
|
+
|
|
124
|
+
// Pattern: shallowRef + markRaw for external library instances
|
|
125
|
+
export function useMapbox(containerId) {
|
|
126
|
+
const map = shallowRef(null)
|
|
127
|
+
|
|
128
|
+
onMounted(() => {
|
|
129
|
+
const instance = new mapboxgl.Map({
|
|
130
|
+
container: containerId,
|
|
131
|
+
style: 'mapbox://styles/mapbox/streets-v11'
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// Mark raw to prevent any proxy wrapping
|
|
135
|
+
map.value = markRaw(instance)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
onUnmounted(() => {
|
|
139
|
+
map.value?.remove()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
return { map }
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Reference
|
|
147
|
+
- [Vue.js markRaw() API](https://vuejs.org/api/reactivity-advanced.html#markraw)
|
|
148
|
+
- [Vue.js Reducing Reactivity Overhead](https://vuejs.org/guide/best-practices/performance.html#reduce-reactivity-overhead-for-large-immutable-structures)
|
|
149
|
+
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Comparing Reactive Objects with === Operator
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Reactive proxies have different identity than original objects - comparison bugs are silent and hard to debug
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, reactivity, proxy, comparison, debugging, identity]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Avoid Comparing Reactive Objects with === Operator
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Vue's `reactive()` returns a Proxy wrapper that has a different identity than the original object. Using `===` to compare reactive objects can lead to silent bugs where comparisons unexpectedly return `false`.
|
|
12
|
+
|
|
13
|
+
When you wrap an object with `reactive()`, the returned proxy is NOT equal to the original object. Additionally, accessing nested objects from a reactive object returns new proxy wrappers each time, which can cause identity comparison issues.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never compare reactive object instances with `===` directly
|
|
18
|
+
- [ ] Use unique identifiers (ID, UUID) for object comparison instead
|
|
19
|
+
- [ ] Use `toRaw()` on both sides when identity comparison is absolutely necessary
|
|
20
|
+
- [ ] Consider using primitive identifiers from database records for comparison
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { reactive } from 'vue'
|
|
25
|
+
|
|
26
|
+
const original = { id: 1, name: 'Item' }
|
|
27
|
+
const state = reactive(original)
|
|
28
|
+
|
|
29
|
+
// BUG: Always returns false - proxy !== original
|
|
30
|
+
if (state === original) {
|
|
31
|
+
console.log('Same object') // Never executes
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// BUG: Nested object comparison fails
|
|
35
|
+
const items = reactive([{ id: 1 }, { id: 2 }])
|
|
36
|
+
const item = items[0]
|
|
37
|
+
|
|
38
|
+
// Later...
|
|
39
|
+
if (items[0] === item) {
|
|
40
|
+
// May or may not work depending on Vue's proxy caching
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// BUG: Comparing items from different reactive sources
|
|
44
|
+
const listA = reactive([{ id: 1 }])
|
|
45
|
+
const listB = reactive([{ id: 1 }])
|
|
46
|
+
if (listA[0] === listB[0]) {
|
|
47
|
+
// Never true, even though they represent the same data
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct:**
|
|
52
|
+
```javascript
|
|
53
|
+
import { reactive, toRaw } from 'vue'
|
|
54
|
+
|
|
55
|
+
const original = { id: 1, name: 'Item' }
|
|
56
|
+
const state = reactive(original)
|
|
57
|
+
|
|
58
|
+
// CORRECT: Use toRaw() for identity comparison
|
|
59
|
+
if (toRaw(state) === original) {
|
|
60
|
+
console.log('Same underlying object') // Works!
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// BEST: Use unique identifiers instead
|
|
64
|
+
const items = reactive([
|
|
65
|
+
{ id: 'uuid-1', name: 'Item 1' },
|
|
66
|
+
{ id: 'uuid-2', name: 'Item 2' }
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
function findItem(targetId) {
|
|
70
|
+
return items.find(item => item.id === targetId)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isSelected(item) {
|
|
74
|
+
return selectedId.value === item.id // Compare IDs, not objects
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// CORRECT: For Set/Map operations, use primitive keys
|
|
78
|
+
const selectedIds = reactive(new Set())
|
|
79
|
+
selectedIds.add(item.id) // Use ID, not object
|
|
80
|
+
selectedIds.has(item.id) // Check by ID
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// When you must compare objects, use toRaw on both sides
|
|
85
|
+
import { toRaw, isReactive } from 'vue'
|
|
86
|
+
|
|
87
|
+
function areEqual(a, b) {
|
|
88
|
+
const rawA = isReactive(a) ? toRaw(a) : a
|
|
89
|
+
const rawB = isReactive(b) ? toRaw(b) : b
|
|
90
|
+
return rawA === rawB
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Reference
|
|
95
|
+
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
|
|
96
|
+
- [Vue.js toRaw() API](https://vuejs.org/api/reactivity-advanced.html#toraw)
|