@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,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Never Mutate Computed Property Return Values
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Mutating computed values causes silent failures and lost changes
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, computed, reactivity, immutability, common-mistake]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Never Mutate Computed Property Return Values
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - The returned value from a computed property is derived state - a temporary snapshot. Mutating this value leads to bugs that are difficult to debug.
|
|
12
|
+
|
|
13
|
+
**Important:** Mutations DO persist while the computed cache remains valid, but are lost when recomputation occurs. The danger lies in unpredictable cache invalidation timing - any change to the computed's dependencies triggers recomputation, silently discarding your mutations. This makes bugs intermittent and hard to reproduce.
|
|
14
|
+
|
|
15
|
+
Every time the source state changes, a new snapshot is created. Mutating a snapshot is meaningless because it will be discarded on the next recalculation.
|
|
16
|
+
|
|
17
|
+
## Task Checklist
|
|
18
|
+
|
|
19
|
+
- [ ] Treat computed return values as read-only
|
|
20
|
+
- [ ] Update the source state instead of the computed value
|
|
21
|
+
- [ ] Use writable computed properties if bidirectional binding is needed
|
|
22
|
+
- [ ] Avoid array mutating methods (push, pop, splice, reverse, sort) on computed arrays
|
|
23
|
+
|
|
24
|
+
**Incorrect:**
|
|
25
|
+
```vue
|
|
26
|
+
<script setup>
|
|
27
|
+
import { ref, computed } from 'vue'
|
|
28
|
+
|
|
29
|
+
const books = ref(['Vue Guide', 'React Handbook'])
|
|
30
|
+
|
|
31
|
+
const publishedBooks = computed(() => {
|
|
32
|
+
return books.value.filter(book => book.includes('Guide'))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function addBook() {
|
|
36
|
+
// BAD: Mutating computed value - change will be lost!
|
|
37
|
+
publishedBooks.value.push('New Book')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// BAD: Mutating computed array
|
|
41
|
+
const sortedBooks = computed(() => books.value.filter(b => b))
|
|
42
|
+
|
|
43
|
+
function reverseBooks() {
|
|
44
|
+
// BAD: This mutates the computed snapshot
|
|
45
|
+
sortedBooks.value.reverse()
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<script>
|
|
52
|
+
export default {
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
author: {
|
|
56
|
+
name: 'John',
|
|
57
|
+
books: ['Book A', 'Book B']
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
computed: {
|
|
62
|
+
authorBooks() {
|
|
63
|
+
return this.author.books
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
methods: {
|
|
67
|
+
addBook() {
|
|
68
|
+
// BAD: Mutating computed value
|
|
69
|
+
this.authorBooks.push('New Book')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Correct:**
|
|
77
|
+
```vue
|
|
78
|
+
<script setup>
|
|
79
|
+
import { ref, computed } from 'vue'
|
|
80
|
+
|
|
81
|
+
const books = ref(['Vue Guide', 'React Handbook'])
|
|
82
|
+
|
|
83
|
+
const publishedBooks = computed(() => {
|
|
84
|
+
return books.value.filter(book => book.includes('Guide'))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
function addBook(bookName) {
|
|
88
|
+
// GOOD: Update the source state
|
|
89
|
+
books.value.push(bookName)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// GOOD: Create a copy before mutating for display
|
|
93
|
+
const sortedBooks = computed(() => {
|
|
94
|
+
return [...books.value].sort() // Spread to create copy before sort
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const reversedBooks = computed(() => {
|
|
98
|
+
return [...books.value].reverse() // Spread to create copy before reverse
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<script>
|
|
105
|
+
export default {
|
|
106
|
+
data() {
|
|
107
|
+
return {
|
|
108
|
+
author: {
|
|
109
|
+
name: 'John',
|
|
110
|
+
books: ['Book A', 'Book B']
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
computed: {
|
|
115
|
+
authorBooks() {
|
|
116
|
+
return this.author.books
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
methods: {
|
|
120
|
+
addBook(bookName) {
|
|
121
|
+
// GOOD: Update source state
|
|
122
|
+
this.author.books.push(bookName)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Writable Computed for Bidirectional Binding
|
|
130
|
+
|
|
131
|
+
If you genuinely need to "set" a computed value, use a writable computed property:
|
|
132
|
+
|
|
133
|
+
```vue
|
|
134
|
+
<script setup>
|
|
135
|
+
import { ref, computed } from 'vue'
|
|
136
|
+
|
|
137
|
+
const firstName = ref('John')
|
|
138
|
+
const lastName = ref('Doe')
|
|
139
|
+
|
|
140
|
+
// Writable computed with getter and setter
|
|
141
|
+
const fullName = computed({
|
|
142
|
+
get() {
|
|
143
|
+
return `${firstName.value} ${lastName.value}`
|
|
144
|
+
},
|
|
145
|
+
set(newValue) {
|
|
146
|
+
// Update source state based on the new value
|
|
147
|
+
const parts = newValue.split(' ')
|
|
148
|
+
firstName.value = parts[0] || ''
|
|
149
|
+
lastName.value = parts[1] || ''
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Now this is valid:
|
|
154
|
+
fullName.value = 'Jane Smith' // Updates firstName and lastName
|
|
155
|
+
</script>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Reference
|
|
159
|
+
- [Vue.js Computed Properties - Avoid Mutating Computed Value](https://vuejs.org/guide/essentials/computed.html#avoid-mutating-computed-value)
|
|
160
|
+
- [Vue.js Computed Properties - Writable Computed](https://vuejs.org/guide/essentials/computed.html#writable-computed)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Configure Vue App Before Calling mount()
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: App configurations after mount() are silently ignored, causing missing plugins and handlers
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, createApp, mount, configuration, setup]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Configure Vue App Before Calling mount()
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Any app configurations applied after `.mount()` is called are silently ignored. This includes error handlers, global components, directives, and plugins, leading to mysterious missing functionality.
|
|
12
|
+
|
|
13
|
+
The `.mount()` method should always be called after all app configurations and asset registrations are done. This is a critical ordering requirement that, when violated, produces no errors but causes features to silently fail.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Register all plugins (router, store, etc.) before mount()
|
|
18
|
+
- [ ] Configure error handlers before mount()
|
|
19
|
+
- [ ] Register global components and directives before mount()
|
|
20
|
+
- [ ] Set all `app.config` properties before mount()
|
|
21
|
+
- [ ] Call `.mount()` as the final step in app initialization
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```javascript
|
|
25
|
+
import { createApp } from 'vue'
|
|
26
|
+
import App from './App.vue'
|
|
27
|
+
import router from './router'
|
|
28
|
+
|
|
29
|
+
const app = createApp(App)
|
|
30
|
+
|
|
31
|
+
// WRONG: Mounting first, then configuring
|
|
32
|
+
app.mount('#app')
|
|
33
|
+
|
|
34
|
+
// These are silently IGNORED - app is already mounted!
|
|
35
|
+
app.use(router)
|
|
36
|
+
app.config.errorHandler = (err) => {
|
|
37
|
+
console.error('Global error:', err)
|
|
38
|
+
}
|
|
39
|
+
app.component('GlobalButton', GlobalButton)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Correct:**
|
|
43
|
+
```javascript
|
|
44
|
+
import { createApp } from 'vue'
|
|
45
|
+
import App from './App.vue'
|
|
46
|
+
import router from './router'
|
|
47
|
+
import { createPinia } from 'pinia'
|
|
48
|
+
import GlobalButton from './components/GlobalButton.vue'
|
|
49
|
+
|
|
50
|
+
const app = createApp(App)
|
|
51
|
+
|
|
52
|
+
// Configure everything FIRST
|
|
53
|
+
app.use(router)
|
|
54
|
+
app.use(createPinia())
|
|
55
|
+
|
|
56
|
+
// Set up error handling
|
|
57
|
+
app.config.errorHandler = (err, instance, info) => {
|
|
58
|
+
console.error('Global error:', err)
|
|
59
|
+
console.log('Component:', instance)
|
|
60
|
+
console.log('Error info:', info)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Register global components
|
|
64
|
+
app.component('GlobalButton', GlobalButton)
|
|
65
|
+
|
|
66
|
+
// Mount LAST - after all configuration is complete
|
|
67
|
+
app.mount('#app')
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Common Mistake: Chaining with Mount
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
// WRONG: Chaining mount in the middle of configuration
|
|
74
|
+
createApp(App)
|
|
75
|
+
.use(router)
|
|
76
|
+
.mount('#app') // Everything after this line is a problem
|
|
77
|
+
.use(pinia) // This doesn't even work - mount returns component instance!
|
|
78
|
+
|
|
79
|
+
// CORRECT: Either complete chain before mount, or use intermediate variable
|
|
80
|
+
createApp(App)
|
|
81
|
+
.use(router)
|
|
82
|
+
.use(pinia)
|
|
83
|
+
.component('GlobalButton', GlobalButton)
|
|
84
|
+
.mount('#app') // Mount at the very end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Reference
|
|
88
|
+
- [Vue.js - Creating a Vue Application](https://vuejs.org/guide/essentials/application.html)
|
|
89
|
+
- [Vue.js Application API](https://vuejs.org/api/application.html)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Declare Emits for Documentation and Validation
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Undeclared emits cause warnings, break TypeScript inference, and prevent event validation
|
|
5
|
+
type: best-practice
|
|
6
|
+
tags: [vue3, emits, defineEmits, component-events, typescript, documentation]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Always Declare Emits for Documentation and Validation
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Declaring emitted events with `defineEmits()` or the `emits` option is technically optional, but strongly recommended. Without declarations, Vue shows runtime warnings, TypeScript can't infer event types, and you lose the ability to validate event payloads.
|
|
12
|
+
|
|
13
|
+
Declared emits also serve as self-documentation, making it immediately clear what events a component can emit.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use `defineEmits()` in `<script setup>` to declare all events
|
|
18
|
+
- [ ] Use `emits` option when not using `<script setup>`
|
|
19
|
+
- [ ] Add TypeScript types for event payloads
|
|
20
|
+
- [ ] Consider adding validation functions for complex payloads
|
|
21
|
+
- [ ] Document the purpose of each event
|
|
22
|
+
|
|
23
|
+
## The Warning
|
|
24
|
+
|
|
25
|
+
When you emit without declaring:
|
|
26
|
+
|
|
27
|
+
```vue
|
|
28
|
+
<script setup>
|
|
29
|
+
// No defineEmits declaration
|
|
30
|
+
function handleClick() {
|
|
31
|
+
emit('select', item) // Vue warns in dev mode
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Vue warns:
|
|
37
|
+
```
|
|
38
|
+
[Vue warn]: Component emitted event "select" but it is neither declared
|
|
39
|
+
in the emits option nor as an "onSelect" prop.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Basic Declaration
|
|
43
|
+
|
|
44
|
+
**Correct - Array syntax:**
|
|
45
|
+
```vue
|
|
46
|
+
<script setup>
|
|
47
|
+
const emit = defineEmits(['submit', 'cancel', 'update'])
|
|
48
|
+
|
|
49
|
+
function handleSubmit() {
|
|
50
|
+
emit('submit', formData)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleCancel() {
|
|
54
|
+
emit('cancel')
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Correct - Options API:**
|
|
60
|
+
```js
|
|
61
|
+
export default {
|
|
62
|
+
emits: ['submit', 'cancel', 'update'],
|
|
63
|
+
|
|
64
|
+
methods: {
|
|
65
|
+
handleSubmit() {
|
|
66
|
+
this.$emit('submit', this.formData)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## TypeScript Typed Emits
|
|
73
|
+
|
|
74
|
+
**Correct - Type-based declaration (recommended for TypeScript):**
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
interface User {
|
|
78
|
+
id: number
|
|
79
|
+
name: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const emit = defineEmits<{
|
|
83
|
+
submit: [data: FormData]
|
|
84
|
+
cancel: []
|
|
85
|
+
'update:modelValue': [value: string]
|
|
86
|
+
select: [user: User, index: number]
|
|
87
|
+
}>()
|
|
88
|
+
|
|
89
|
+
// Now TypeScript enforces correct payloads
|
|
90
|
+
emit('submit', formData) // OK
|
|
91
|
+
emit('submit') // Error: Expected 1 argument
|
|
92
|
+
emit('select', user) // Error: Expected 2 arguments
|
|
93
|
+
emit('unknown') // Error: Unknown event
|
|
94
|
+
</script>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Alternative syntax (Vue 3.3+):**
|
|
98
|
+
```vue
|
|
99
|
+
<script setup lang="ts">
|
|
100
|
+
const emit = defineEmits<{
|
|
101
|
+
(e: 'submit', data: FormData): void
|
|
102
|
+
(e: 'cancel'): void
|
|
103
|
+
(e: 'update:modelValue', value: string): void
|
|
104
|
+
}>()
|
|
105
|
+
</script>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Event Validation
|
|
109
|
+
|
|
110
|
+
You can validate event payloads at runtime:
|
|
111
|
+
|
|
112
|
+
**Correct - Validation functions:**
|
|
113
|
+
```vue
|
|
114
|
+
<script setup>
|
|
115
|
+
const emit = defineEmits({
|
|
116
|
+
// No validation, just declaration
|
|
117
|
+
cancel: null,
|
|
118
|
+
|
|
119
|
+
// Validate payload
|
|
120
|
+
submit: (payload) => {
|
|
121
|
+
if (!payload.email) {
|
|
122
|
+
console.warn('Submit event requires email')
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
return true
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Validate with type checking
|
|
129
|
+
click: (id) => typeof id === 'number'
|
|
130
|
+
})
|
|
131
|
+
</script>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Returning `false` from a validator logs a console warning but doesn't prevent the event from being emitted.
|
|
135
|
+
|
|
136
|
+
## Benefits of Declaring Emits
|
|
137
|
+
|
|
138
|
+
### 1. Fallthrough Attribute Separation
|
|
139
|
+
|
|
140
|
+
Without declaration, native event listeners fall through to the root element:
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
<!-- ParentComponent.vue -->
|
|
144
|
+
<ChildComponent @click="handleClick" />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```vue
|
|
148
|
+
<!-- ChildComponent.vue - WITHOUT emits declaration -->
|
|
149
|
+
<template>
|
|
150
|
+
<!-- Native click listener falls through to button -->
|
|
151
|
+
<button>Click me</button>
|
|
152
|
+
</template>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
With declaration, Vue knows it's a component event:
|
|
156
|
+
|
|
157
|
+
```vue
|
|
158
|
+
<script setup>
|
|
159
|
+
// Now Vue knows 'click' is a component event, not native
|
|
160
|
+
const emit = defineEmits(['click'])
|
|
161
|
+
</script>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. Self-Documentation
|
|
165
|
+
|
|
166
|
+
```vue
|
|
167
|
+
<script setup>
|
|
168
|
+
// Clear contract: this component emits these events
|
|
169
|
+
const emit = defineEmits<{
|
|
170
|
+
'row-click': [row: TableRow]
|
|
171
|
+
'row-select': [row: TableRow, selected: boolean]
|
|
172
|
+
'page-change': [page: number]
|
|
173
|
+
'sort-change': [column: string, direction: 'asc' | 'desc']
|
|
174
|
+
}>()
|
|
175
|
+
</script>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 3. IDE Support
|
|
179
|
+
|
|
180
|
+
With declarations, your IDE can:
|
|
181
|
+
- Autocomplete event names when using the component
|
|
182
|
+
- Show event payload types
|
|
183
|
+
- Warn about typos in event names
|
|
184
|
+
- Navigate to event definitions
|
|
185
|
+
|
|
186
|
+
## $emit in Template vs emit in Script
|
|
187
|
+
|
|
188
|
+
```vue
|
|
189
|
+
<script setup>
|
|
190
|
+
// $emit is available in template, but...
|
|
191
|
+
// emit() is needed in <script setup>
|
|
192
|
+
const emit = defineEmits(['submit'])
|
|
193
|
+
|
|
194
|
+
function handleSubmit() {
|
|
195
|
+
// $emit doesn't work here - use emit()
|
|
196
|
+
emit('submit', data)
|
|
197
|
+
}
|
|
198
|
+
</script>
|
|
199
|
+
|
|
200
|
+
<template>
|
|
201
|
+
<!-- $emit works in template -->
|
|
202
|
+
<button @click="$emit('submit', data)">Submit</button>
|
|
203
|
+
|
|
204
|
+
<!-- Or use the declared emit function -->
|
|
205
|
+
<button @click="emit('submit', data)">Submit</button>
|
|
206
|
+
</template>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Reference
|
|
210
|
+
- [Vue.js Component Events - Declaring Emitted Events](https://vuejs.org/guide/components/events.html#declaring-emitted-events)
|
|
211
|
+
- [Vue.js Component Events - Events Validation](https://vuejs.org/guide/components/events.html#events-validation)
|
|
212
|
+
- [Vue 3 Migration - emits Option](https://v3-migration.vuejs.org/breaking-changes/emits-option)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineExpose Must Be Called Before Any Await
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Properties exposed after await are inaccessible to parent component refs
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, script-setup, defineExpose, async, component-refs]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# defineExpose Must Be Called Before Any Await
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - In `<script setup>`, if you call `defineExpose()` after an `await` statement, the exposed properties will NOT be accessible to parent components using template refs. This is a subtle async timing issue that causes silent failures.
|
|
12
|
+
|
|
13
|
+
The compiler transforms top-level await, and code after await runs in a different execution context where defineExpose cannot properly register with the component instance.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Always call defineExpose() at the top of script setup, before any await
|
|
18
|
+
- [ ] If async data is needed in exposed methods, fetch it separately
|
|
19
|
+
- [ ] Structure code so expose declarations come first
|
|
20
|
+
- [ ] Test parent ref access when using async setup
|
|
21
|
+
|
|
22
|
+
**Incorrect:**
|
|
23
|
+
```vue
|
|
24
|
+
<!-- ChildComponent.vue -->
|
|
25
|
+
<script setup>
|
|
26
|
+
import { ref } from 'vue'
|
|
27
|
+
|
|
28
|
+
const data = ref(null)
|
|
29
|
+
const count = ref(0)
|
|
30
|
+
|
|
31
|
+
function increment() {
|
|
32
|
+
count.value++
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// WRONG: await before defineExpose
|
|
36
|
+
const response = await fetch('/api/data')
|
|
37
|
+
data.value = await response.json()
|
|
38
|
+
|
|
39
|
+
// BROKEN: This won't work - called after await!
|
|
40
|
+
defineExpose({
|
|
41
|
+
count,
|
|
42
|
+
increment,
|
|
43
|
+
data
|
|
44
|
+
})
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div>{{ data }}</div>
|
|
49
|
+
</template>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<!-- ParentComponent.vue -->
|
|
54
|
+
<script setup>
|
|
55
|
+
import { ref, onMounted } from 'vue'
|
|
56
|
+
import ChildComponent from './ChildComponent.vue'
|
|
57
|
+
|
|
58
|
+
const childRef = ref(null)
|
|
59
|
+
|
|
60
|
+
onMounted(() => {
|
|
61
|
+
// FAILS: All exposed properties are undefined!
|
|
62
|
+
console.log(childRef.value.count) // undefined
|
|
63
|
+
childRef.value.increment() // TypeError
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<Suspense>
|
|
69
|
+
<ChildComponent ref="childRef" />
|
|
70
|
+
</Suspense>
|
|
71
|
+
</template>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Correct:**
|
|
75
|
+
```vue
|
|
76
|
+
<!-- ChildComponent.vue -->
|
|
77
|
+
<script setup>
|
|
78
|
+
import { ref } from 'vue'
|
|
79
|
+
|
|
80
|
+
const data = ref(null)
|
|
81
|
+
const count = ref(0)
|
|
82
|
+
|
|
83
|
+
function increment() {
|
|
84
|
+
count.value++
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// CORRECT: defineExpose BEFORE any await
|
|
88
|
+
defineExpose({
|
|
89
|
+
count,
|
|
90
|
+
increment,
|
|
91
|
+
data
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Now safe to use await
|
|
95
|
+
const response = await fetch('/api/data')
|
|
96
|
+
data.value = await response.json()
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<template>
|
|
100
|
+
<div>{{ data }}</div>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```vue
|
|
105
|
+
<!-- Alternative: Separate async logic from expose -->
|
|
106
|
+
<script setup>
|
|
107
|
+
import { ref, onMounted } from 'vue'
|
|
108
|
+
|
|
109
|
+
const data = ref(null)
|
|
110
|
+
const loading = ref(true)
|
|
111
|
+
|
|
112
|
+
function getData() {
|
|
113
|
+
return data.value
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function refreshData() {
|
|
117
|
+
loading.value = true
|
|
118
|
+
const response = await fetch('/api/data')
|
|
119
|
+
data.value = await response.json()
|
|
120
|
+
loading.value = false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// CORRECT: No await at top level - defineExpose always works
|
|
124
|
+
defineExpose({
|
|
125
|
+
data,
|
|
126
|
+
getData,
|
|
127
|
+
refreshData,
|
|
128
|
+
loading
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Trigger async load in lifecycle hook instead
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
refreshData()
|
|
134
|
+
})
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<div v-if="loading">Loading...</div>
|
|
139
|
+
<div v-else>{{ data }}</div>
|
|
140
|
+
</template>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```vue
|
|
144
|
+
<!-- If you must use top-level await, define expose first -->
|
|
145
|
+
<script setup>
|
|
146
|
+
import { ref } from 'vue'
|
|
147
|
+
|
|
148
|
+
const user = ref(null)
|
|
149
|
+
const posts = ref([])
|
|
150
|
+
|
|
151
|
+
// CORRECT: All expose calls come first
|
|
152
|
+
defineExpose({
|
|
153
|
+
user,
|
|
154
|
+
posts,
|
|
155
|
+
refresh: () => loadData()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// Now safe to await
|
|
159
|
+
async function loadData() {
|
|
160
|
+
const [userRes, postsRes] = await Promise.all([
|
|
161
|
+
fetch('/api/user'),
|
|
162
|
+
fetch('/api/posts')
|
|
163
|
+
])
|
|
164
|
+
user.value = await userRes.json()
|
|
165
|
+
posts.value = await postsRes.json()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Top-level await after defineExpose is safe
|
|
169
|
+
await loadData()
|
|
170
|
+
</script>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Why This Happens
|
|
174
|
+
|
|
175
|
+
Vue's compiler transforms `<script setup>` with top-level await into an async setup function. The component instance context is only available synchronously before the first await. After await, the execution resumes outside that context, making defineExpose ineffective.
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
// What the compiler roughly generates:
|
|
179
|
+
async setup() {
|
|
180
|
+
const count = ref(0)
|
|
181
|
+
|
|
182
|
+
// Context available here
|
|
183
|
+
await fetch(...) // Suspends execution
|
|
184
|
+
|
|
185
|
+
// Context lost after resuming
|
|
186
|
+
defineExpose({ count }) // Too late!
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Reference
|
|
191
|
+
- [Vue.js Script Setup - defineExpose](https://vuejs.org/api/sfc-script-setup.html#defineexpose)
|
|
192
|
+
- [Vue.js Async Components](https://vuejs.org/guide/components/async.html)
|