@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,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Understand Reactive Updates are Batched Per Event Loop Tick
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Multiple synchronous reactive changes are batched - watchers only see the final value, not intermediate states
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, reactivity, batching, event-loop, watchers, nextTick]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Understand Reactive Updates are Batched Per Event Loop Tick
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Vue batches multiple reactive state changes that happen synchronously within the same event loop tick. Watchers and computed properties only see the final state, not intermediate values. This is an optimization, but it can be surprising if you expect watchers to fire for each individual change.
|
|
12
|
+
|
|
13
|
+
Understanding this behavior is essential for debugging scenarios where you expect to observe every state transition.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Understand watchers fire once per tick with final value, not for each mutation
|
|
18
|
+
- [ ] Use `nextTick()` if you need to ensure DOM updates between state changes
|
|
19
|
+
- [ ] Use `flush: 'sync'` on watchers only if you absolutely need immediate execution
|
|
20
|
+
- [ ] For intermediate value tracking, consider logging or explicit state snapshots
|
|
21
|
+
|
|
22
|
+
**Example of batching behavior:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref, watch } from 'vue'
|
|
25
|
+
|
|
26
|
+
const count = ref(0)
|
|
27
|
+
|
|
28
|
+
watch(count, (newValue) => {
|
|
29
|
+
console.log('Count changed to:', newValue)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Multiple synchronous changes in the same tick
|
|
33
|
+
function multipleUpdates() {
|
|
34
|
+
count.value = 1
|
|
35
|
+
count.value = 2
|
|
36
|
+
count.value = 3
|
|
37
|
+
count.value = 4
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
multipleUpdates()
|
|
41
|
+
// Console output: "Count changed to: 4"
|
|
42
|
+
// NOT: 1, 2, 3, 4 - only the final value is observed!
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**The console logs you WON'T see:**
|
|
46
|
+
```javascript
|
|
47
|
+
const items = reactive([])
|
|
48
|
+
|
|
49
|
+
watch(items, (newItems) => {
|
|
50
|
+
console.log('Items count:', newItems.length)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Batch of changes
|
|
54
|
+
items.push('a') // length: 1
|
|
55
|
+
items.push('b') // length: 2
|
|
56
|
+
items.push('c') // length: 3
|
|
57
|
+
|
|
58
|
+
// Output: "Items count: 3"
|
|
59
|
+
// You won't see 1, 2, 3 logged separately
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Using flush: 'sync' for immediate watching (use with caution):**
|
|
63
|
+
```javascript
|
|
64
|
+
import { ref, watch } from 'vue'
|
|
65
|
+
|
|
66
|
+
const count = ref(0)
|
|
67
|
+
|
|
68
|
+
// Sync watcher fires immediately on each change
|
|
69
|
+
watch(count, (newValue) => {
|
|
70
|
+
console.log('Immediate:', newValue)
|
|
71
|
+
}, { flush: 'sync' })
|
|
72
|
+
|
|
73
|
+
count.value = 1 // Logs: "Immediate: 1"
|
|
74
|
+
count.value = 2 // Logs: "Immediate: 2"
|
|
75
|
+
count.value = 3 // Logs: "Immediate: 3"
|
|
76
|
+
|
|
77
|
+
// WARNING: flush: 'sync' can cause performance issues
|
|
78
|
+
// and creates less predictable behavior. Avoid if possible.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Using nextTick to separate batches:**
|
|
82
|
+
```javascript
|
|
83
|
+
import { ref, watch, nextTick } from 'vue'
|
|
84
|
+
|
|
85
|
+
const count = ref(0)
|
|
86
|
+
|
|
87
|
+
watch(count, (newValue) => {
|
|
88
|
+
console.log('Count:', newValue)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
async function separatedUpdates() {
|
|
92
|
+
count.value = 1
|
|
93
|
+
await nextTick() // Force flush
|
|
94
|
+
// Output: "Count: 1"
|
|
95
|
+
|
|
96
|
+
count.value = 2
|
|
97
|
+
await nextTick()
|
|
98
|
+
// Output: "Count: 2"
|
|
99
|
+
|
|
100
|
+
count.value = 3
|
|
101
|
+
// Output: "Count: 3"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Practical example - form validation:**
|
|
106
|
+
```javascript
|
|
107
|
+
const formData = reactive({
|
|
108
|
+
email: '',
|
|
109
|
+
password: ''
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const validationErrors = ref([])
|
|
113
|
+
|
|
114
|
+
// This watcher only fires once, with final form state
|
|
115
|
+
watch(formData, (data) => {
|
|
116
|
+
// Runs once after all fields are updated
|
|
117
|
+
validateForm(data)
|
|
118
|
+
}, { deep: true })
|
|
119
|
+
|
|
120
|
+
// When user submits, you might update multiple fields
|
|
121
|
+
function populateFromSavedData(saved) {
|
|
122
|
+
formData.email = saved.email
|
|
123
|
+
formData.password = saved.password
|
|
124
|
+
// Validation runs once with both fields set
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**When batching helps performance:**
|
|
129
|
+
```javascript
|
|
130
|
+
// Without batching, this would trigger 1000 watcher/render cycles
|
|
131
|
+
const list = reactive([])
|
|
132
|
+
|
|
133
|
+
function addManyItems() {
|
|
134
|
+
for (let i = 0; i < 1000; i++) {
|
|
135
|
+
list.push(i)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// With batching: renders once with all 1000 items
|
|
139
|
+
// Without batching: would render 1000 times!
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Debugging intermediate states:**
|
|
143
|
+
```javascript
|
|
144
|
+
// If you need to observe every change for debugging:
|
|
145
|
+
import { ref, watch } from 'vue'
|
|
146
|
+
|
|
147
|
+
const count = ref(0)
|
|
148
|
+
|
|
149
|
+
// Method 1: Sync watcher (not recommended for production)
|
|
150
|
+
watch(count, (val) => console.log('DEBUG:', val), { flush: 'sync' })
|
|
151
|
+
|
|
152
|
+
// Method 2: Track history manually
|
|
153
|
+
const history = []
|
|
154
|
+
const originalSet = count.value
|
|
155
|
+
Object.defineProperty(count, 'value', {
|
|
156
|
+
set(val) {
|
|
157
|
+
history.push(val)
|
|
158
|
+
originalSet.call(this, val)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Reference
|
|
164
|
+
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
|
|
165
|
+
- [Vue.js Watchers - Callback Flush Timing](https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing)
|
|
166
|
+
- [Vue.js nextTick()](https://vuejs.org/api/general.html#nexttick)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Use .value When Accessing ref() in JavaScript
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Forgetting .value causes silent failures and bugs in reactive state updates
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, reactivity, ref, composition-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Always Use .value When Accessing ref() in JavaScript
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Forgetting `.value` causes silent failures where state updates don't trigger reactivity, leading to hard-to-debug issues.
|
|
12
|
+
|
|
13
|
+
When using `ref()` in Vue 3's Composition API, the reactive value is wrapped in an object and must be accessed via `.value` in JavaScript code. However, in templates, Vue automatically unwraps refs so `.value` is not needed there. This inconsistency is a common source of bugs.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always use `.value` when reading or writing ref values in `<script>` or `.js`/`.ts` files
|
|
18
|
+
- [ ] Never use `.value` in `<template>` - Vue unwraps refs automatically there
|
|
19
|
+
- [ ] When passing refs to functions, decide whether to pass the ref object or `.value`
|
|
20
|
+
- [ ] Use IDE/TypeScript to catch missing `.value` errors early
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref } from 'vue'
|
|
25
|
+
|
|
26
|
+
const count = ref(0)
|
|
27
|
+
|
|
28
|
+
// These do NOT work as expected
|
|
29
|
+
count++ // Tries to increment the ref object, not the value
|
|
30
|
+
count = 5 // Reassigns the variable, loses reactivity
|
|
31
|
+
console.log(count) // Logs "[object Object]", not the number
|
|
32
|
+
|
|
33
|
+
const items = ref([1, 2, 3])
|
|
34
|
+
items.push(4) // Error: push is not a function
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Correct:**
|
|
38
|
+
```javascript
|
|
39
|
+
import { ref } from 'vue'
|
|
40
|
+
|
|
41
|
+
const count = ref(0)
|
|
42
|
+
|
|
43
|
+
// Always use .value in JavaScript
|
|
44
|
+
count.value++ // Correctly increments to 1
|
|
45
|
+
count.value = 5 // Correctly sets value to 5
|
|
46
|
+
console.log(count.value) // Logs "5"
|
|
47
|
+
|
|
48
|
+
const items = ref([1, 2, 3])
|
|
49
|
+
items.value.push(4) // Correctly adds 4 to the array
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<template>
|
|
54
|
+
<!-- In templates, NO .value needed - Vue unwraps automatically -->
|
|
55
|
+
<p>{{ count }}</p>
|
|
56
|
+
<button @click="count++">Increment</button>
|
|
57
|
+
</template>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Reference
|
|
61
|
+
- [Vue.js Reactivity Fundamentals - ref()](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Refs in Arrays and Collections Require .value
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Refs inside reactive arrays, Maps, or Sets are NOT auto-unwrapped like in reactive objects
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, reactivity, ref, arrays, collections, unwrapping]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Refs in Arrays and Collections Require .value
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Unlike when a ref is a property of a reactive object, refs inside reactive arrays, Maps, and Sets are NOT automatically unwrapped. You must access them with `.value`, and forgetting this leads to silent bugs.
|
|
12
|
+
|
|
13
|
+
Vue only auto-unwraps refs when they are properties of reactive objects. When refs are elements in arrays or values in Maps/Sets, they remain as ref objects and require explicit `.value` access.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always use `.value` when accessing refs stored in reactive arrays
|
|
18
|
+
- [ ] Always use `.value` when accessing refs stored in reactive Maps or Sets
|
|
19
|
+
- [ ] Consider storing plain values instead of refs in collections to avoid confusion
|
|
20
|
+
- [ ] Be aware of this when iterating over arrays containing refs
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { ref, reactive } from 'vue'
|
|
25
|
+
|
|
26
|
+
const books = reactive([ref('Vue 3 Guide')])
|
|
27
|
+
const counts = reactive(new Map([['clicks', ref(0)]]))
|
|
28
|
+
|
|
29
|
+
// WRONG: Refs in arrays are NOT unwrapped
|
|
30
|
+
console.log(books[0]) // Ref object, not 'Vue 3 Guide'
|
|
31
|
+
books[0] = 'New Title' // Replaces the ref, doesn't update it!
|
|
32
|
+
|
|
33
|
+
// WRONG: Refs in Maps are NOT unwrapped
|
|
34
|
+
console.log(counts.get('clicks')) // Ref object, not 0
|
|
35
|
+
counts.get('clicks')++ // Does nothing useful
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Correct:**
|
|
39
|
+
```javascript
|
|
40
|
+
import { ref, reactive } from 'vue'
|
|
41
|
+
|
|
42
|
+
const books = reactive([ref('Vue 3 Guide')])
|
|
43
|
+
const counts = reactive(new Map([['clicks', ref(0)]]))
|
|
44
|
+
|
|
45
|
+
// CORRECT: Use .value for refs in arrays
|
|
46
|
+
console.log(books[0].value) // 'Vue 3 Guide'
|
|
47
|
+
books[0].value = 'New Title' // Updates the ref's value
|
|
48
|
+
|
|
49
|
+
// CORRECT: Use .value for refs in Maps
|
|
50
|
+
console.log(counts.get('clicks').value) // 0
|
|
51
|
+
counts.get('clicks').value++ // Increments to 1
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// ALTERNATIVE: Just store plain values in collections (simpler)
|
|
56
|
+
const books = reactive(['Vue 3 Guide', 'Vuex Handbook'])
|
|
57
|
+
const counts = reactive(new Map([['clicks', 0]]))
|
|
58
|
+
|
|
59
|
+
// No .value needed - but changes to individual items aren't independently reactive
|
|
60
|
+
console.log(books[0]) // 'Vue 3 Guide'
|
|
61
|
+
console.log(counts.get('clicks')) // 0
|
|
62
|
+
|
|
63
|
+
// Mutations still trigger reactivity through the reactive wrapper
|
|
64
|
+
books[0] = 'New Title' // Works
|
|
65
|
+
counts.set('clicks', counts.get('clicks') + 1) // Works
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```vue
|
|
69
|
+
<template>
|
|
70
|
+
<!-- In templates, refs in arrays also need special handling -->
|
|
71
|
+
<div v-for="(book, index) in books" :key="index">
|
|
72
|
+
<!-- If book is a ref, you'd need: -->
|
|
73
|
+
{{ book.value }}
|
|
74
|
+
|
|
75
|
+
<!-- Or use computed to unwrap them first -->
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Reference
|
|
81
|
+
- [Vue.js Reactivity Fundamentals - Caveat in Arrays and Collections](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#caveat-in-arrays-and-collections)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Rely on Internal VNode Properties
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Using undocumented vnode properties causes code to break on Vue updates
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, render-function, vnode, internal-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Do Not Rely on Internal VNode Properties
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - The `VNode` interface contains many internal properties used by Vue's rendering system. Relying on any properties other than the documented public ones will cause your code to break when Vue's internal implementation changes.
|
|
12
|
+
|
|
13
|
+
Only use the documented vnode properties: `type`, `props`, `children`, and `key`. All other properties are internal implementation details that may change without notice between Vue versions.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Only access documented vnode properties: `type`, `props`, `children`, `key`
|
|
18
|
+
- [ ] Never access properties like `el`, `component`, `shapeFlag`, `patchFlag`, etc.
|
|
19
|
+
- [ ] If you need DOM element access, use template refs instead
|
|
20
|
+
- [ ] Treat vnodes as opaque data structures for rendering, not inspection
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { h } from 'vue'
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
setup(props, { slots }) {
|
|
28
|
+
return () => {
|
|
29
|
+
const slotContent = slots.default?.()
|
|
30
|
+
|
|
31
|
+
// WRONG: Accessing internal properties
|
|
32
|
+
if (slotContent?.[0]?.el) {
|
|
33
|
+
// el is an internal property
|
|
34
|
+
console.log(slotContent[0].el.tagName)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// WRONG: Using shapeFlag internal property
|
|
38
|
+
if (slotContent?.[0]?.shapeFlag & 1) {
|
|
39
|
+
// This is internal implementation
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return h('div', slotContent)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// WRONG: Inspecting component instance via vnode
|
|
50
|
+
const vnode = h(MyComponent)
|
|
51
|
+
console.log(vnode.component) // Internal property
|
|
52
|
+
console.log(vnode.appContext) // Internal property
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Correct:**
|
|
56
|
+
```javascript
|
|
57
|
+
import { h } from 'vue'
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
setup(props, { slots }) {
|
|
61
|
+
return () => {
|
|
62
|
+
const slotContent = slots.default?.()
|
|
63
|
+
|
|
64
|
+
// CORRECT: Only use documented properties
|
|
65
|
+
if (slotContent?.[0]) {
|
|
66
|
+
const vnode = slotContent[0]
|
|
67
|
+
console.log(vnode.type) // Safe: element type or component
|
|
68
|
+
console.log(vnode.props) // Safe: props object
|
|
69
|
+
console.log(vnode.children) // Safe: children
|
|
70
|
+
console.log(vnode.key) // Safe: key prop
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return h('div', slotContent)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
import { h, ref, onMounted } from 'vue'
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
setup() {
|
|
84
|
+
// CORRECT: Use template refs for DOM access
|
|
85
|
+
const divRef = ref(null)
|
|
86
|
+
|
|
87
|
+
onMounted(() => {
|
|
88
|
+
// Safe way to access DOM element
|
|
89
|
+
console.log(divRef.value.tagName)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return () => h('div', { ref: divRef }, 'Content')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Documented VNode Properties
|
|
98
|
+
|
|
99
|
+
| Property | Type | Description |
|
|
100
|
+
|----------|------|-------------|
|
|
101
|
+
| `type` | `string \| Component` | Element tag name or component definition |
|
|
102
|
+
| `props` | `object \| null` | Props passed to the vnode |
|
|
103
|
+
| `children` | `any` | Child vnodes, text, or slots |
|
|
104
|
+
| `key` | `string \| number \| null` | Key for list rendering |
|
|
105
|
+
|
|
106
|
+
## Safe VNode Inspection Patterns
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
import { h, isVNode } from 'vue'
|
|
110
|
+
|
|
111
|
+
export default {
|
|
112
|
+
setup(props, { slots }) {
|
|
113
|
+
return () => {
|
|
114
|
+
const children = slots.default?.() || []
|
|
115
|
+
|
|
116
|
+
// Safe: Check if something is a vnode
|
|
117
|
+
children.forEach(child => {
|
|
118
|
+
if (isVNode(child)) {
|
|
119
|
+
// Safe: Check vnode type
|
|
120
|
+
if (typeof child.type === 'string') {
|
|
121
|
+
console.log('Element:', child.type)
|
|
122
|
+
} else if (typeof child.type === 'object') {
|
|
123
|
+
console.log('Component:', child.type.name)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Safe: Read props
|
|
127
|
+
if (child.props?.class) {
|
|
128
|
+
console.log('Has class:', child.props.class)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return h('div', children)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Why This Matters
|
|
140
|
+
|
|
141
|
+
Vue's internal vnode structure may change for:
|
|
142
|
+
- Performance optimizations
|
|
143
|
+
- New feature implementations
|
|
144
|
+
- Bug fixes
|
|
145
|
+
- Tree-shaking improvements
|
|
146
|
+
|
|
147
|
+
Code relying on internal properties will break silently or throw errors when upgrading Vue versions. The documented properties are part of Vue's public API and are guaranteed to remain stable.
|
|
148
|
+
|
|
149
|
+
## Reference
|
|
150
|
+
- [Vue.js Render Function APIs](https://vuejs.org/api/render-function.html)
|
|
151
|
+
- [Vue.js Render Functions - The Virtual DOM](https://vuejs.org/guide/extras/render-function.html#the-virtual-dom)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: VNodes Must Be Unique in Render Functions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Reusing vnode references causes rendering bugs and unexpected behavior
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, render-function, vnode, composition-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# VNodes Must Be Unique in Render Functions
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Reusing the same vnode reference multiple times in a render function tree causes rendering bugs, where only one instance appears or updates behave unexpectedly.
|
|
12
|
+
|
|
13
|
+
Every vnode in a component's render tree must be unique. You cannot use the same vnode object multiple times. If you need to render the same element multiple times, create each vnode separately using a factory function or by calling `h()` in a loop.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never store a vnode in a variable and use it multiple times in the same tree
|
|
18
|
+
- [ ] Use a factory function or `.map()` to create multiple similar vnodes
|
|
19
|
+
- [ ] Each `h()` call creates a new vnode, so call it for each instance needed
|
|
20
|
+
- [ ] Be especially careful when extracting vnode creation into helper functions
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```javascript
|
|
24
|
+
import { h } from 'vue'
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
setup() {
|
|
28
|
+
return () => {
|
|
29
|
+
// WRONG: Same vnode reference used twice
|
|
30
|
+
const p = h('p', 'Hello')
|
|
31
|
+
return h('div', [p, p]) // Bug! Duplicate vnode reference
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import { h } from 'vue'
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
setup() {
|
|
42
|
+
return () => {
|
|
43
|
+
// WRONG: Reusing vnode in different parts of tree
|
|
44
|
+
const icon = h('span', { class: 'icon' }, '★')
|
|
45
|
+
return h('div', [
|
|
46
|
+
h('button', [icon, ' Save']), // Uses icon
|
|
47
|
+
h('button', [icon, ' Delete']) // Reuses same icon - Bug!
|
|
48
|
+
])
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Correct:**
|
|
55
|
+
```javascript
|
|
56
|
+
import { h } from 'vue'
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
setup() {
|
|
60
|
+
return () => {
|
|
61
|
+
// CORRECT: Create new vnode for each use
|
|
62
|
+
return h('div', [
|
|
63
|
+
h('p', 'Hello'),
|
|
64
|
+
h('p', 'Hello')
|
|
65
|
+
])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import { h } from 'vue'
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
setup() {
|
|
76
|
+
return () => {
|
|
77
|
+
// CORRECT: Factory function creates new vnode each time
|
|
78
|
+
const createIcon = () => h('span', { class: 'icon' }, '★')
|
|
79
|
+
return h('div', [
|
|
80
|
+
h('button', [createIcon(), ' Save']),
|
|
81
|
+
h('button', [createIcon(), ' Delete'])
|
|
82
|
+
])
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
import { h } from 'vue'
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
setup() {
|
|
93
|
+
return () => {
|
|
94
|
+
// CORRECT: Using map to create multiple vnodes
|
|
95
|
+
return h('div',
|
|
96
|
+
Array.from({ length: 20 }).map(() => h('p', 'Hello'))
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { h } from 'vue'
|
|
105
|
+
|
|
106
|
+
export default {
|
|
107
|
+
setup() {
|
|
108
|
+
const items = ['Apple', 'Banana', 'Cherry']
|
|
109
|
+
|
|
110
|
+
return () => h('ul',
|
|
111
|
+
// CORRECT: Each iteration creates a new vnode
|
|
112
|
+
items.map((item, index) =>
|
|
113
|
+
h('li', { key: index }, item)
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Why VNodes Must Be Unique
|
|
121
|
+
|
|
122
|
+
VNodes are lightweight JavaScript objects that Vue's virtual DOM algorithm uses for diffing and patching. When the same vnode reference appears multiple times:
|
|
123
|
+
- Vue cannot differentiate between the instances
|
|
124
|
+
- The diffing algorithm produces incorrect results
|
|
125
|
+
- Only one instance may render, or updates may corrupt the DOM
|
|
126
|
+
|
|
127
|
+
Each vnode maintains its own identity and position in the tree, which is essential for:
|
|
128
|
+
- Correct DOM patching during updates
|
|
129
|
+
- Proper lifecycle hook execution
|
|
130
|
+
- Accurate key-based reconciliation in lists
|
|
131
|
+
|
|
132
|
+
## Reference
|
|
133
|
+
- [Vue.js Render Functions - Vnodes Must Be Unique](https://vuejs.org/guide/extras/render-function.html#vnodes-must-be-unique)
|