@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,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Call toValue() Inside watchEffect for Proper Dependency Tracking
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Calling toValue() outside watchEffect prevents reactive dependency tracking, causing the effect to never re-run
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, composables, composition-api, watchEffect, toValue, reactivity]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Call toValue() Inside watchEffect for Proper Dependency Tracking
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When writing composables that accept `MaybeRefOrGetter` arguments, you must call `toValue()` inside the `watchEffect` callback, not outside. If you extract the value before the watchEffect, Vue cannot track the dependency and the effect will never re-run when the source changes.
|
|
12
|
+
|
|
13
|
+
This is a subtle but critical mistake that leads to composables that work with initial values but never update.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always call `toValue()` inside `watchEffect` callbacks, not before
|
|
18
|
+
- [ ] Similarly, access `.value` on refs inside watchEffect, not outside
|
|
19
|
+
- [ ] For `watch()`, use a getter function that calls `toValue()`
|
|
20
|
+
- [ ] Test that composables update when their inputs change
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref, watchEffect, toValue } from 'vue'
|
|
25
|
+
|
|
26
|
+
export function useFetch(url) {
|
|
27
|
+
const data = ref(null)
|
|
28
|
+
const error = ref(null)
|
|
29
|
+
|
|
30
|
+
// WRONG: toValue called outside watchEffect
|
|
31
|
+
// This extracts the value ONCE and passes a static string
|
|
32
|
+
const urlValue = toValue(url)
|
|
33
|
+
|
|
34
|
+
watchEffect(async () => {
|
|
35
|
+
try {
|
|
36
|
+
// urlValue is a static string - no dependency tracked!
|
|
37
|
+
const response = await fetch(urlValue)
|
|
38
|
+
data.value = await response.json()
|
|
39
|
+
} catch (e) {
|
|
40
|
+
error.value = e
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return { data, error }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// When used like this:
|
|
48
|
+
const apiUrl = ref('/api/users')
|
|
49
|
+
const { data } = useFetch(apiUrl)
|
|
50
|
+
|
|
51
|
+
// Later...
|
|
52
|
+
apiUrl.value = '/api/products' // useFetch will NOT refetch!
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Correct:**
|
|
56
|
+
```javascript
|
|
57
|
+
import { ref, watchEffect, toValue } from 'vue'
|
|
58
|
+
|
|
59
|
+
export function useFetch(url) {
|
|
60
|
+
const data = ref(null)
|
|
61
|
+
const error = ref(null)
|
|
62
|
+
|
|
63
|
+
watchEffect(async () => {
|
|
64
|
+
// CORRECT: toValue called INSIDE watchEffect
|
|
65
|
+
// Vue tracks this as a dependency
|
|
66
|
+
const urlValue = toValue(url)
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(urlValue)
|
|
70
|
+
data.value = await response.json()
|
|
71
|
+
} catch (e) {
|
|
72
|
+
error.value = e
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return { data, error }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Now when used:
|
|
80
|
+
const apiUrl = ref('/api/users')
|
|
81
|
+
const { data } = useFetch(apiUrl)
|
|
82
|
+
|
|
83
|
+
// Later...
|
|
84
|
+
apiUrl.value = '/api/products' // useFetch WILL refetch!
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## The Same Applies to Direct Ref Access
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// WRONG: Accessing .value outside the effect
|
|
91
|
+
export function useDebounce(source, delay = 300) {
|
|
92
|
+
// This captures the initial value, not a reactive dependency
|
|
93
|
+
const initialValue = source.value // or toValue(source)
|
|
94
|
+
|
|
95
|
+
watchEffect(() => {
|
|
96
|
+
// initialValue is static - this only runs once
|
|
97
|
+
console.log('Value:', initialValue)
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// CORRECT: Access inside the effect
|
|
102
|
+
export function useDebounce(source, delay = 300) {
|
|
103
|
+
watchEffect(() => {
|
|
104
|
+
// Vue tracks source.value or toValue(source) as dependency
|
|
105
|
+
console.log('Value:', toValue(source))
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Pattern: Using watch() with Getter Functions
|
|
111
|
+
|
|
112
|
+
For `watch()`, wrap `toValue()` in a getter:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
import { ref, watch, toValue } from 'vue'
|
|
116
|
+
|
|
117
|
+
export function useLocalStorage(key, defaultValue) {
|
|
118
|
+
const data = ref(defaultValue)
|
|
119
|
+
|
|
120
|
+
// CORRECT: Use getter function with watch
|
|
121
|
+
watch(
|
|
122
|
+
() => toValue(key), // Getter calls toValue, tracks dependency
|
|
123
|
+
(newKey) => {
|
|
124
|
+
const stored = localStorage.getItem(newKey)
|
|
125
|
+
data.value = stored ? JSON.parse(stored) : defaultValue
|
|
126
|
+
},
|
|
127
|
+
{ immediate: true }
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return data
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Why This Happens
|
|
135
|
+
|
|
136
|
+
Vue's reactivity tracking works by detecting property accesses during effect execution:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
watchEffect(() => {
|
|
140
|
+
// When this runs, Vue is "recording" what reactive sources are accessed
|
|
141
|
+
const value = someRef.value // Vue records: "this effect depends on someRef"
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// But if you extract the value before:
|
|
145
|
+
const value = someRef.value // Vue isn't recording yet
|
|
146
|
+
watchEffect(() => {
|
|
147
|
+
console.log(value) // Just using a plain JavaScript variable
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`toValue()` works the same way - it accesses `.value` internally, so it must happen during effect execution for tracking to work.
|
|
152
|
+
|
|
153
|
+
## Quick Checklist for Composable Authors
|
|
154
|
+
|
|
155
|
+
When accepting `MaybeRefOrGetter` inputs:
|
|
156
|
+
|
|
157
|
+
1. Store the raw argument (don't call `toValue` during setup)
|
|
158
|
+
2. Call `toValue()` inside any reactive context (`watchEffect`, `watch`, `computed`)
|
|
159
|
+
3. Test with both static values AND refs that change
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
export function useMyComposable(input) {
|
|
163
|
+
// Store raw - don't extract value here
|
|
164
|
+
// const value = toValue(input) // WRONG
|
|
165
|
+
|
|
166
|
+
const result = computed(() => {
|
|
167
|
+
// Extract value inside reactive context
|
|
168
|
+
return transform(toValue(input)) // CORRECT
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
watchEffect(() => {
|
|
172
|
+
// Extract value inside reactive context
|
|
173
|
+
doSomething(toValue(input)) // CORRECT
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
return { result }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Reference
|
|
181
|
+
- [Vue.js Reactivity API - toValue](https://vuejs.org/api/reactivity-utilities.html#tovalue)
|
|
182
|
+
- [Vue.js Composables - Accepting Ref Arguments](https://vuejs.org/guide/reusability/composables.html#accepting-reactive-state)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Composition API Uses Mutable Reactivity, Not Functional Programming
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Misunderstanding the paradigm leads to incorrect state management patterns
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, composition-api, reactivity, functional-programming, paradigm]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Composition API Uses Mutable Reactivity, Not Functional Programming
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Despite being function-based, the Composition API follows Vue's mutable, fine-grained reactivity paradigm—NOT functional programming principles. Treating it like a functional paradigm leads to incorrect patterns like unnecessary cloning, immutable-style updates, or avoiding mutation when mutation is the intended pattern.
|
|
12
|
+
|
|
13
|
+
Vue's Composition API leverages imported functions to organize code, but the underlying model is based on mutable reactive state that Vue tracks and responds to. This is fundamentally different from functional programming with immutability (like Redux reducers).
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Mutate reactive state directly - don't create new objects for every update
|
|
18
|
+
- [ ] Don't apply immutability patterns unnecessarily (spreading, Object.assign for updates)
|
|
19
|
+
- [ ] Understand that `ref()` and `reactive()` enable mutable state tracking
|
|
20
|
+
- [ ] Use Vue's reactivity as intended: direct mutation with automatic tracking
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref } from 'vue'
|
|
25
|
+
|
|
26
|
+
const todos = ref([])
|
|
27
|
+
|
|
28
|
+
// WRONG: Treating Vue like Redux/functional - unnecessary immutability
|
|
29
|
+
function addTodo(todo) {
|
|
30
|
+
// Creating a new array every time is wasteful in Vue
|
|
31
|
+
todos.value = [...todos.value, todo]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function updateTodo(id, updates) {
|
|
35
|
+
// Unnecessary spread - Vue tracks mutations directly
|
|
36
|
+
todos.value = todos.value.map(t =>
|
|
37
|
+
t.id === id ? { ...t, ...updates } : t
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const user = ref({ name: 'John', age: 30 })
|
|
42
|
+
|
|
43
|
+
// WRONG: Creating new object for simple update
|
|
44
|
+
function updateName(newName) {
|
|
45
|
+
user.value = { ...user.value, name: newName }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Correct:**
|
|
50
|
+
```javascript
|
|
51
|
+
import { ref, reactive } from 'vue'
|
|
52
|
+
|
|
53
|
+
const todos = ref([])
|
|
54
|
+
|
|
55
|
+
// CORRECT: Mutate directly - Vue tracks the change
|
|
56
|
+
function addTodo(todo) {
|
|
57
|
+
todos.value.push(todo) // Direct mutation is the Vue way
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function updateTodo(id, updates) {
|
|
61
|
+
const todo = todos.value.find(t => t.id === id)
|
|
62
|
+
if (todo) {
|
|
63
|
+
Object.assign(todo, updates) // Direct mutation
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const user = ref({ name: 'John', age: 30 })
|
|
68
|
+
|
|
69
|
+
// CORRECT: Mutate the property directly
|
|
70
|
+
function updateName(newName) {
|
|
71
|
+
user.value.name = newName // Vue tracks this!
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Or with reactive():
|
|
75
|
+
const state = reactive({ name: 'John', age: 30 })
|
|
76
|
+
|
|
77
|
+
function updateNameReactive(newName) {
|
|
78
|
+
state.name = newName // Direct mutation, reactivity preserved
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## When Immutability Patterns Make Sense
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Immutability IS appropriate when:
|
|
86
|
+
|
|
87
|
+
// 1. Replacing the entire state (e.g., from API response)
|
|
88
|
+
const users = ref([])
|
|
89
|
+
async function fetchUsers() {
|
|
90
|
+
users.value = await api.getUsers() // Complete replacement is fine
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. When you need a snapshot for comparison
|
|
94
|
+
const previousState = { ...currentState } // For undo/redo
|
|
95
|
+
|
|
96
|
+
// 3. When passing data to external libraries expecting immutable data
|
|
97
|
+
const chartData = computed(() => [...rawData.value]) // Copy for chart lib
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## The Vue Mental Model
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Vue's reactivity is like a spreadsheet:
|
|
104
|
+
// - Cell A1 contains a value (ref)
|
|
105
|
+
// - Cell B1 has a formula referencing A1 (computed)
|
|
106
|
+
// - Change A1, and B1 automatically updates
|
|
107
|
+
|
|
108
|
+
const a1 = ref(10)
|
|
109
|
+
const b1 = computed(() => a1.value * 2)
|
|
110
|
+
|
|
111
|
+
// You CHANGE A1 (mutate), you don't create a new A1
|
|
112
|
+
a1.value = 20 // b1 automatically becomes 40
|
|
113
|
+
|
|
114
|
+
// This is fundamentally different from:
|
|
115
|
+
// state = reducer(state, action) // Functional/Redux pattern
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Reference
|
|
119
|
+
- [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html)
|
|
120
|
+
- [Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Top-Level await in script setup Preserves Component Context
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Misunderstanding async context causes lifecycle hooks and watchers to silently fail
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, composition-api, script-setup, async, await, suspense]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Top-Level await in script setup Preserves Component Context
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - In `<script setup>`, top-level `await` statements preserve component context (allowing lifecycle hooks and watchers after `await`), but this is a special case. Nested async functions or callbacks lose context, causing lifecycle hooks to silently fail.
|
|
12
|
+
|
|
13
|
+
Vue's compiler automatically injects context restoration after each top-level await in `<script setup>`. This doesn't apply to `setup()` function or nested async contexts.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Understand that top-level await in `<script setup>` is specially handled
|
|
18
|
+
- [ ] Never register lifecycle hooks in nested async functions
|
|
19
|
+
- [ ] Use `<Suspense>` when using async `<script setup>` components
|
|
20
|
+
- [ ] In regular `setup()`, never use await before lifecycle hook registration
|
|
21
|
+
- [ ] Register hooks synchronously, then do async work inside them
|
|
22
|
+
|
|
23
|
+
**Top-Level await Works (script setup only):**
|
|
24
|
+
```vue
|
|
25
|
+
<script setup>
|
|
26
|
+
import { ref, onMounted, watch } from 'vue'
|
|
27
|
+
|
|
28
|
+
// This is TOP-LEVEL await - Vue compiler preserves context
|
|
29
|
+
const config = await fetchConfig() // OK!
|
|
30
|
+
|
|
31
|
+
// These hooks work because Vue restored context
|
|
32
|
+
onMounted(() => {
|
|
33
|
+
console.log('This will run!') // Works
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
watch(someRef, () => {
|
|
37
|
+
console.log('This will track!') // Works
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Another top-level await - still OK
|
|
41
|
+
const data = await fetchData(config.apiUrl) // OK!
|
|
42
|
+
|
|
43
|
+
// Still works after multiple awaits
|
|
44
|
+
onMounted(() => {
|
|
45
|
+
console.log('This also runs!') // Works
|
|
46
|
+
})
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<!-- IMPORTANT: Parent must use Suspense -->
|
|
50
|
+
<template>
|
|
51
|
+
<Suspense>
|
|
52
|
+
<AsyncComponent />
|
|
53
|
+
</Suspense>
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Nested Async Breaks Context:**
|
|
58
|
+
```vue
|
|
59
|
+
<script setup>
|
|
60
|
+
import { ref, onMounted, watch } from 'vue'
|
|
61
|
+
|
|
62
|
+
// WRONG: Nested async function - context lost after await
|
|
63
|
+
async function initializeData() {
|
|
64
|
+
const config = await fetchConfig()
|
|
65
|
+
|
|
66
|
+
// BUG: This hook will NOT be registered!
|
|
67
|
+
// We're no longer in the synchronous setup context
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
console.log('This will NEVER run!') // Silent failure
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// BUG: This watcher won't auto-dispose on unmount
|
|
73
|
+
watch(someRef, () => {
|
|
74
|
+
console.log('Memory leak - not cleaned up!')
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Calling the async function
|
|
79
|
+
initializeData() // Hooks inside won't work!
|
|
80
|
+
|
|
81
|
+
// WRONG: Callbacks also lose context
|
|
82
|
+
setTimeout(async () => {
|
|
83
|
+
await delay(100)
|
|
84
|
+
onMounted(() => {
|
|
85
|
+
console.log('Never runs!') // Silent failure
|
|
86
|
+
})
|
|
87
|
+
}, 0)
|
|
88
|
+
</script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Correct Patterns:**
|
|
92
|
+
```vue
|
|
93
|
+
<script setup>
|
|
94
|
+
import { ref, onMounted, watch } from 'vue'
|
|
95
|
+
|
|
96
|
+
const data = ref(null)
|
|
97
|
+
const config = ref(null)
|
|
98
|
+
|
|
99
|
+
// CORRECT: Register hooks synchronously FIRST
|
|
100
|
+
onMounted(async () => {
|
|
101
|
+
// Then do async work INSIDE the hook
|
|
102
|
+
config.value = await fetchConfig()
|
|
103
|
+
data.value = await fetchData(config.value.apiUrl)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// CORRECT: Watchers registered synchronously
|
|
107
|
+
watch(config, async (newConfig) => {
|
|
108
|
+
if (newConfig) {
|
|
109
|
+
data.value = await fetchData(newConfig.apiUrl)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Or use top-level await for initial data
|
|
114
|
+
const initialConfig = await fetchConfig() // OK - top level
|
|
115
|
+
config.value = initialConfig
|
|
116
|
+
|
|
117
|
+
onMounted(() => {
|
|
118
|
+
console.log('Works!') // Context preserved by compiler
|
|
119
|
+
})
|
|
120
|
+
</script>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**setup() Function (Not script setup):**
|
|
124
|
+
```javascript
|
|
125
|
+
// In regular setup(), await ALWAYS breaks context
|
|
126
|
+
export default {
|
|
127
|
+
async setup() {
|
|
128
|
+
const data = ref(null)
|
|
129
|
+
|
|
130
|
+
// WRONG: Hooks after await won't register
|
|
131
|
+
const config = await fetchConfig()
|
|
132
|
+
|
|
133
|
+
onMounted(() => {
|
|
134
|
+
console.log('Never runs!') // Silent failure!
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return { data }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// CORRECT: Register hooks before any await
|
|
142
|
+
export default {
|
|
143
|
+
async setup() {
|
|
144
|
+
const data = ref(null)
|
|
145
|
+
|
|
146
|
+
// Register hooks FIRST (synchronous)
|
|
147
|
+
onMounted(async () => {
|
|
148
|
+
const config = await fetchConfig()
|
|
149
|
+
data.value = await fetchData(config)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Now you can await if needed
|
|
153
|
+
// But hooks must be registered before this point
|
|
154
|
+
|
|
155
|
+
return { data }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Why This Happens
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// Vue tracks the "current component instance" during setup
|
|
164
|
+
// This is like a global variable that gets set and cleared
|
|
165
|
+
|
|
166
|
+
// During synchronous setup:
|
|
167
|
+
function setup() {
|
|
168
|
+
currentInstance = this // Vue sets this
|
|
169
|
+
|
|
170
|
+
onMounted(cb) // Uses currentInstance to register
|
|
171
|
+
|
|
172
|
+
// After await, JavaScript resumes in a microtask
|
|
173
|
+
await something()
|
|
174
|
+
|
|
175
|
+
// currentInstance is now null or different!
|
|
176
|
+
onMounted(cb) // Can't find the instance - silently fails
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// <script setup> compiler adds restoration:
|
|
180
|
+
// After each await, it injects: setCurrentInstance(savedInstance)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Suspense Requirement
|
|
184
|
+
|
|
185
|
+
```vue
|
|
186
|
+
<!-- When using async script setup, parent needs Suspense -->
|
|
187
|
+
<template>
|
|
188
|
+
<Suspense>
|
|
189
|
+
<!-- Async component with top-level await -->
|
|
190
|
+
<AsyncChild />
|
|
191
|
+
|
|
192
|
+
<!-- Optional: Loading state -->
|
|
193
|
+
<template #fallback>
|
|
194
|
+
<LoadingSpinner />
|
|
195
|
+
</template>
|
|
196
|
+
</Suspense>
|
|
197
|
+
</template>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Reference
|
|
201
|
+
- [Composition API FAQ - Async Setup](https://vuejs.org/guide/extras/composition-api-faq.html)
|
|
202
|
+
- [Composables - Async Without Await](https://antfu.me/posts/async-with-composition-api)
|
|
203
|
+
- [Suspense](https://vuejs.org/guide/built-ins/suspense.html)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Vue Composition API Runs Once, Unlike React Hooks
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Understanding this difference prevents over-engineering and React patterns that don't apply
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, composition-api, react-hooks, setup, stale-closure]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Vue Composition API Runs Once, Unlike React Hooks
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue's `setup()` or `<script setup>` executes only once per component instance, while React Hooks run on every render. Developers coming from React often apply patterns (dependency arrays, excessive memoization, useCallback) that are unnecessary and counterproductive in Vue.
|
|
12
|
+
|
|
13
|
+
Understanding this fundamental difference is crucial for writing idiomatic Vue code. Vue's approach eliminates entire categories of bugs (stale closures, exhaustive deps) that plague React applications.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Don't implement "dependency arrays" - Vue tracks dependencies automatically
|
|
18
|
+
- [ ] Don't wrap functions in "useCallback" equivalents - not needed in Vue
|
|
19
|
+
- [ ] Don't use "useMemo" patterns - Vue's `computed()` handles this automatically
|
|
20
|
+
- [ ] Understand that closures in Vue don't go "stale" like in React
|
|
21
|
+
- [ ] Don't worry about "call order" - Vue composables can be conditional
|
|
22
|
+
|
|
23
|
+
**React Patterns to Avoid in Vue:**
|
|
24
|
+
```javascript
|
|
25
|
+
// These patterns are UNNECESSARY in Vue - they solve React-specific problems
|
|
26
|
+
|
|
27
|
+
// WRONG: Trying to implement dependency arrays (React pattern)
|
|
28
|
+
watch(
|
|
29
|
+
[dep1, dep2, dep3], // Vue tracks deps automatically in watchEffect
|
|
30
|
+
() => {
|
|
31
|
+
// ...
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
// Unless you specifically WANT to control which deps trigger the watcher,
|
|
35
|
+
// prefer watchEffect() which auto-tracks
|
|
36
|
+
|
|
37
|
+
// WRONG: Memoizing callbacks like useCallback
|
|
38
|
+
const memoizedHandler = computed(() => {
|
|
39
|
+
return () => doSomething(state.value)
|
|
40
|
+
})
|
|
41
|
+
// In Vue, just define the function normally - no memoization needed
|
|
42
|
+
|
|
43
|
+
// WRONG: Worrying about stale closures
|
|
44
|
+
function useData() {
|
|
45
|
+
const data = ref(null)
|
|
46
|
+
|
|
47
|
+
// In React, this could capture stale 'data' - NOT in Vue!
|
|
48
|
+
// Vue refs are always current
|
|
49
|
+
const handler = () => {
|
|
50
|
+
console.log(data.value) // Always gets current value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { data, handler }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Correct Vue Patterns:**
|
|
58
|
+
```javascript
|
|
59
|
+
import { ref, computed, watchEffect } from 'vue'
|
|
60
|
+
|
|
61
|
+
// CORRECT: Auto-dependency tracking with watchEffect
|
|
62
|
+
const query = ref('')
|
|
63
|
+
const filter = ref('all')
|
|
64
|
+
|
|
65
|
+
watchEffect(() => {
|
|
66
|
+
// Vue automatically detects that this depends on query and filter
|
|
67
|
+
// No dependency array needed!
|
|
68
|
+
fetchResults(query.value, filter.value)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// CORRECT: computed() handles memoization automatically
|
|
72
|
+
const expensiveResult = computed(() => {
|
|
73
|
+
// Only recalculates when dependencies actually change
|
|
74
|
+
return heavyComputation(data.value)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// CORRECT: Functions don't need memoization
|
|
78
|
+
function handleClick() {
|
|
79
|
+
count.value++
|
|
80
|
+
}
|
|
81
|
+
// Just use it directly - no useCallback wrapper needed
|
|
82
|
+
// <button @click="handleClick">
|
|
83
|
+
|
|
84
|
+
// CORRECT: Closures always access current values
|
|
85
|
+
const count = ref(0)
|
|
86
|
+
const message = ref('')
|
|
87
|
+
|
|
88
|
+
function logState() {
|
|
89
|
+
// This always logs CURRENT values, never stale ones
|
|
90
|
+
console.log(`Count: ${count.value}, Message: ${message.value}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
logState() // Gets current values even if called later
|
|
95
|
+
}, 5000)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Vue's Advantages Over React Hooks
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// 1. No stale closure problems
|
|
102
|
+
const count = ref(0)
|
|
103
|
+
|
|
104
|
+
onMounted(() => {
|
|
105
|
+
setInterval(() => {
|
|
106
|
+
// In React: would need useRef or deps array to avoid stale value
|
|
107
|
+
// In Vue: count.value is always current
|
|
108
|
+
console.log(count.value)
|
|
109
|
+
}, 1000)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// 2. Composables can be conditional
|
|
113
|
+
if (featureEnabled) {
|
|
114
|
+
const { data } = useSomeFeature() // This is FINE in Vue!
|
|
115
|
+
}
|
|
116
|
+
// In React: "Hooks cannot be conditional" - not a problem in Vue
|
|
117
|
+
|
|
118
|
+
// 3. No exhaustive-deps linting headaches
|
|
119
|
+
watchEffect(() => {
|
|
120
|
+
// Use any reactive values - Vue tracks them all automatically
|
|
121
|
+
// No ESLint rule yelling about missing dependencies
|
|
122
|
+
doSomething(a.value, b.value, c.value)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// 4. Child components don't need memoization by default
|
|
126
|
+
// Vue's reactivity system only updates what actually changed
|
|
127
|
+
// No need for React.memo() equivalents in most cases
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## When Vue Patterns Differ
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// Setup runs once - so initialization happens once
|
|
134
|
+
<script setup>
|
|
135
|
+
import { ref, onMounted } from 'vue'
|
|
136
|
+
|
|
137
|
+
// This code runs ONCE when component is created
|
|
138
|
+
const data = ref(null)
|
|
139
|
+
console.log('Setup running') // Only logs once
|
|
140
|
+
|
|
141
|
+
onMounted(() => {
|
|
142
|
+
console.log('Mounted') // Only logs once
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// If you need something to run on every reactive change,
|
|
146
|
+
// use watch or watchEffect
|
|
147
|
+
watchEffect(() => {
|
|
148
|
+
// This runs when dependencies change
|
|
149
|
+
console.log('Data changed:', data.value)
|
|
150
|
+
})
|
|
151
|
+
</script>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Reference
|
|
155
|
+
- [Composition API FAQ - Relationship with React Hooks](https://vuejs.org/guide/extras/composition-api-faq.html#relationship-with-react-hooks)
|
|
156
|
+
- [Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)
|