@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,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Imported Types Have Limitations in defineProps
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Complex imported types like conditional types can cause compiler errors in defineProps
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, typescript, defineProps, imported-types, vue3.3]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Imported Types Have Limitations in defineProps
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - While Vue 3.3+ supports imported types in `defineProps<>()`, certain complex types are not fully supported. Conditional types, mapped types that require full type analysis, and global ambient types can cause "Unresolvable type reference" errors.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Understand which type patterns are supported vs unsupported
|
|
16
|
+
- [ ] Keep prop type definitions simple and explicit
|
|
17
|
+
- [ ] Move complex type logic outside of defineProps
|
|
18
|
+
- [ ] Export interfaces explicitly rather than using global declarations
|
|
19
|
+
|
|
20
|
+
## Supported Patterns (Vue 3.3+)
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// types/user.ts
|
|
24
|
+
export interface User {
|
|
25
|
+
id: string
|
|
26
|
+
name: string
|
|
27
|
+
email?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ListProps<T> {
|
|
31
|
+
items: T[]
|
|
32
|
+
selectedItem?: T
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type Status = 'pending' | 'active' | 'completed'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import type { User, Status } from '@/types/user'
|
|
41
|
+
|
|
42
|
+
// WORKS: Simple imported interface
|
|
43
|
+
defineProps<{
|
|
44
|
+
user: User
|
|
45
|
+
}>()
|
|
46
|
+
|
|
47
|
+
// WORKS: Imported union type
|
|
48
|
+
defineProps<{
|
|
49
|
+
status: Status
|
|
50
|
+
}>()
|
|
51
|
+
|
|
52
|
+
// WORKS: Direct imported interface
|
|
53
|
+
defineProps<User>()
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Unsupported Patterns
|
|
58
|
+
|
|
59
|
+
### Conditional Types for Entire Props Object
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// types/conditional.ts
|
|
63
|
+
export type ConditionalProps<T> = T extends string
|
|
64
|
+
? { value: string; onChange: (v: string) => void }
|
|
65
|
+
: { value: number; onChange: (v: number) => void }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```vue
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
import type { ConditionalProps } from '@/types/conditional'
|
|
71
|
+
|
|
72
|
+
// ERROR: Conditional types not supported for entire props object
|
|
73
|
+
defineProps<ConditionalProps<string>>()
|
|
74
|
+
</script>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Workaround:**
|
|
78
|
+
```vue
|
|
79
|
+
<script setup lang="ts">
|
|
80
|
+
// Define the resolved type directly
|
|
81
|
+
interface StringProps {
|
|
82
|
+
value: string
|
|
83
|
+
onChange: (v: string) => void
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
defineProps<StringProps>()
|
|
87
|
+
</script>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Conditional Types for Individual Props ARE Supported
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<script setup lang="ts">
|
|
94
|
+
// This WORKS - conditional type on individual prop
|
|
95
|
+
interface Props {
|
|
96
|
+
value: SomeType extends string ? string : number // OK
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
defineProps<Props>()
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Global Ambient Types
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// global.d.ts (ambient declaration without export)
|
|
107
|
+
interface GlobalUser {
|
|
108
|
+
id: string
|
|
109
|
+
name: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// No export statement - this is an ambient declaration
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<script setup lang="ts">
|
|
117
|
+
// ERROR: "Unresolvable type reference"
|
|
118
|
+
defineProps<{
|
|
119
|
+
user: GlobalUser // Can't resolve ambient global type
|
|
120
|
+
}>()
|
|
121
|
+
</script>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Workaround:**
|
|
125
|
+
```typescript
|
|
126
|
+
// types/user.ts - Use explicit export
|
|
127
|
+
export interface GlobalUser {
|
|
128
|
+
id: string
|
|
129
|
+
name: string
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```vue
|
|
134
|
+
<script setup lang="ts">
|
|
135
|
+
import type { GlobalUser } from '@/types/user'
|
|
136
|
+
|
|
137
|
+
// WORKS: Explicitly imported
|
|
138
|
+
defineProps<{
|
|
139
|
+
user: GlobalUser
|
|
140
|
+
}>()
|
|
141
|
+
</script>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Complex Mapped Types
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// types/complex.ts
|
|
148
|
+
export type DeepReadonly<T> = {
|
|
149
|
+
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface User {
|
|
153
|
+
id: string
|
|
154
|
+
profile: { name: string }
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```vue
|
|
159
|
+
<script setup lang="ts">
|
|
160
|
+
import type { DeepReadonly, User } from '@/types/complex'
|
|
161
|
+
|
|
162
|
+
// May fail or produce incorrect types
|
|
163
|
+
defineProps<{
|
|
164
|
+
user: DeepReadonly<User>
|
|
165
|
+
}>()
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Workaround:**
|
|
170
|
+
```typescript
|
|
171
|
+
// Resolve the type explicitly
|
|
172
|
+
export interface ReadonlyUser {
|
|
173
|
+
readonly id: string
|
|
174
|
+
readonly profile: { readonly name: string }
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Union of Imported Interfaces
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// types/forms.ts
|
|
182
|
+
export interface TextInput { type: 'text'; value: string }
|
|
183
|
+
export interface NumberInput { type: 'number'; value: number }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```vue
|
|
187
|
+
<script setup lang="ts">
|
|
188
|
+
import type { TextInput, NumberInput } from '@/types/forms'
|
|
189
|
+
|
|
190
|
+
// Can cause issues in some Vue versions
|
|
191
|
+
defineProps<{
|
|
192
|
+
input: TextInput | NumberInput
|
|
193
|
+
}>()
|
|
194
|
+
</script>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Workaround:**
|
|
198
|
+
```typescript
|
|
199
|
+
// Define the union in the types file
|
|
200
|
+
export type AnyInput = TextInput | NumberInput
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```vue
|
|
204
|
+
<script setup lang="ts">
|
|
205
|
+
import type { AnyInput } from '@/types/forms'
|
|
206
|
+
|
|
207
|
+
defineProps<{
|
|
208
|
+
input: AnyInput
|
|
209
|
+
}>()
|
|
210
|
+
</script>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Best Practices
|
|
214
|
+
|
|
215
|
+
### Keep Props Types Simple
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// GOOD: Simple, explicit interface
|
|
219
|
+
export interface ButtonProps {
|
|
220
|
+
variant: 'primary' | 'secondary' | 'danger'
|
|
221
|
+
size: 'sm' | 'md' | 'lg'
|
|
222
|
+
disabled?: boolean
|
|
223
|
+
loading?: boolean
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// AVOID: Over-engineered generic types
|
|
227
|
+
export type ButtonProps<V extends string, S extends string> = {
|
|
228
|
+
variant: V
|
|
229
|
+
size: S
|
|
230
|
+
// ...complex type gymnastics
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Resolve Types Before Export
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Instead of exporting generic utilities
|
|
238
|
+
// export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
|
239
|
+
|
|
240
|
+
// Export the resolved type
|
|
241
|
+
export interface CreateUserProps {
|
|
242
|
+
name: string
|
|
243
|
+
email: string
|
|
244
|
+
age?: number // Made optional
|
|
245
|
+
role?: 'admin' | 'user' // Made optional
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Use Dual Script Blocks for Complex Cases
|
|
250
|
+
|
|
251
|
+
```vue
|
|
252
|
+
<script lang="ts">
|
|
253
|
+
// Regular script block for complex type definitions
|
|
254
|
+
import type { ComplexType } from '@/types'
|
|
255
|
+
|
|
256
|
+
// Resolve the type here
|
|
257
|
+
type ResolvedProps = ComplexType extends SomeCondition
|
|
258
|
+
? { a: string }
|
|
259
|
+
: { b: number }
|
|
260
|
+
</script>
|
|
261
|
+
|
|
262
|
+
<script setup lang="ts">
|
|
263
|
+
// Use the resolved type
|
|
264
|
+
defineProps<ResolvedProps>()
|
|
265
|
+
</script>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Version-Specific Behavior
|
|
269
|
+
|
|
270
|
+
| Vue Version | Imported Types | Complex Types |
|
|
271
|
+
|-------------|---------------|---------------|
|
|
272
|
+
| 3.2 | Not supported | Not supported |
|
|
273
|
+
| 3.3 | Supported | Limited |
|
|
274
|
+
| 3.4+ | Supported | Better support |
|
|
275
|
+
|
|
276
|
+
Always check the Vue changelog for updates to type support in defineProps.
|
|
277
|
+
|
|
278
|
+
## Reference
|
|
279
|
+
- [Vue.js TypeScript with Composition API](https://vuejs.org/guide/typescript/composition-api.html)
|
|
280
|
+
- [GitHub Issue: defineProps with imported interfaces](https://github.com/vuejs/core/issues/8612)
|
|
281
|
+
- [GitHub Issue: Union types in defineProps](https://github.com/vuejs/core/issues/5804)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Explicitly Type Event Handlers
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Without explicit typing, event parameters have implicit 'any' type causing TypeScript errors in strict mode
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, typescript, events, dom-events, composition-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Always Explicitly Type Event Handlers
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Native DOM event handlers in Vue components have implicit `any` type for the `event` parameter. In TypeScript strict mode, this causes errors. You must explicitly type event parameters and use type assertions for `event.target`.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Always type the `event` parameter explicitly (e.g., `Event`, `MouseEvent`)
|
|
16
|
+
- [ ] Use type assertions when accessing element-specific properties
|
|
17
|
+
- [ ] Consider using inline handlers for simple cases
|
|
18
|
+
- [ ] Be aware of Vue's synthetic event system
|
|
19
|
+
|
|
20
|
+
## The Problem
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
// WRONG: event has implicit 'any' type
|
|
25
|
+
function handleChange(event) { // Error in strict mode!
|
|
26
|
+
console.log(event.target.value) // Also error: target might be null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// WRONG: Missing type assertion for element access
|
|
30
|
+
function handleInput(event: Event) {
|
|
31
|
+
console.log(event.target.value) // Error: 'value' doesn't exist on EventTarget
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<input @change="handleChange" @input="handleInput" />
|
|
37
|
+
</template>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The Solution
|
|
41
|
+
|
|
42
|
+
```vue
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
// CORRECT: Explicit Event type + type assertion
|
|
45
|
+
function handleChange(event: Event) {
|
|
46
|
+
const target = event.target as HTMLInputElement
|
|
47
|
+
console.log(target.value)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// CORRECT: Specific event type when needed
|
|
51
|
+
function handleClick(event: MouseEvent) {
|
|
52
|
+
console.log(event.clientX, event.clientY)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
56
|
+
if (event.key === 'Enter') {
|
|
57
|
+
submitForm()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleSubmit(event: SubmitEvent) {
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
const formData = new FormData(event.target as HTMLFormElement)
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<input @change="handleChange" />
|
|
69
|
+
<button @click="handleClick">Click</button>
|
|
70
|
+
<input @keydown="handleKeydown" />
|
|
71
|
+
<form @submit="handleSubmit">...</form>
|
|
72
|
+
</template>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Common Event Types
|
|
76
|
+
|
|
77
|
+
| Event | Type | Common Properties |
|
|
78
|
+
|-------|------|-------------------|
|
|
79
|
+
| click, dblclick | `MouseEvent` | clientX, clientY, button |
|
|
80
|
+
| keydown, keyup, keypress | `KeyboardEvent` | key, code, ctrlKey, shiftKey |
|
|
81
|
+
| input, change | `Event` | target (needs assertion) |
|
|
82
|
+
| focus, blur | `FocusEvent` | relatedTarget |
|
|
83
|
+
| submit | `SubmitEvent` | submitter |
|
|
84
|
+
| drag, dragstart, drop | `DragEvent` | dataTransfer |
|
|
85
|
+
| wheel, scroll | `WheelEvent` | deltaX, deltaY |
|
|
86
|
+
| touch events | `TouchEvent` | touches, changedTouches |
|
|
87
|
+
|
|
88
|
+
## Element-Specific Type Assertions
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
// HTMLInputElement for text, number, checkbox, radio inputs
|
|
93
|
+
function handleTextInput(event: Event) {
|
|
94
|
+
const input = event.target as HTMLInputElement
|
|
95
|
+
console.log(input.value, input.checked)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// HTMLSelectElement for select dropdowns
|
|
99
|
+
function handleSelect(event: Event) {
|
|
100
|
+
const select = event.target as HTMLSelectElement
|
|
101
|
+
console.log(select.value, select.selectedIndex)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// HTMLTextAreaElement for textareas
|
|
105
|
+
function handleTextarea(event: Event) {
|
|
106
|
+
const textarea = event.target as HTMLTextAreaElement
|
|
107
|
+
console.log(textarea.value, textarea.selectionStart)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// HTMLFormElement for forms
|
|
111
|
+
function handleFormSubmit(event: SubmitEvent) {
|
|
112
|
+
event.preventDefault()
|
|
113
|
+
const form = event.target as HTMLFormElement
|
|
114
|
+
const formData = new FormData(form)
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Inline Event Handler Pattern
|
|
120
|
+
|
|
121
|
+
For simple cases, inline handlers with type annotations work well:
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<template>
|
|
125
|
+
<!-- Inline with type assertion -->
|
|
126
|
+
<input
|
|
127
|
+
@input="(event: Event) => {
|
|
128
|
+
const input = event.target as HTMLInputElement
|
|
129
|
+
searchQuery = input.value
|
|
130
|
+
}"
|
|
131
|
+
/>
|
|
132
|
+
|
|
133
|
+
<!-- Or with $event cast -->
|
|
134
|
+
<input @input="searchQuery = ($event.target as HTMLInputElement).value" />
|
|
135
|
+
</template>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Generic Handler Pattern
|
|
139
|
+
|
|
140
|
+
Create reusable typed handlers:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// utils/events.ts
|
|
144
|
+
export function getInputValue(event: Event): string {
|
|
145
|
+
return (event.target as HTMLInputElement).value
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getSelectValue(event: Event): string {
|
|
149
|
+
return (event.target as HTMLSelectElement).value
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function getCheckboxChecked(event: Event): boolean {
|
|
153
|
+
return (event.target as HTMLInputElement).checked
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```vue
|
|
158
|
+
<script setup lang="ts">
|
|
159
|
+
import { getInputValue, getCheckboxChecked } from '@/utils/events'
|
|
160
|
+
|
|
161
|
+
const name = ref('')
|
|
162
|
+
const agreed = ref(false)
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<template>
|
|
166
|
+
<input @input="e => name = getInputValue(e)" />
|
|
167
|
+
<input type="checkbox" @change="e => agreed = getCheckboxChecked(e)" />
|
|
168
|
+
</template>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Vue Component Events
|
|
172
|
+
|
|
173
|
+
For Vue component events (not DOM events), use `defineEmits` for type safety:
|
|
174
|
+
|
|
175
|
+
```vue
|
|
176
|
+
<script setup lang="ts">
|
|
177
|
+
const emit = defineEmits<{
|
|
178
|
+
'custom-event': [data: { id: number; name: string }]
|
|
179
|
+
}>()
|
|
180
|
+
|
|
181
|
+
// Handler for child component event
|
|
182
|
+
function handleChildEvent(data: { id: number; name: string }) {
|
|
183
|
+
console.log(data.id, data.name)
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<!-- Custom component event - properly typed -->
|
|
189
|
+
<ChildComponent @custom-event="handleChildEvent" />
|
|
190
|
+
</template>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Avoiding currentTarget vs target Confusion
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
function handleClick(event: MouseEvent) {
|
|
197
|
+
// target: The element that triggered the event (could be a child)
|
|
198
|
+
const target = event.target as HTMLElement
|
|
199
|
+
|
|
200
|
+
// currentTarget: The element the listener is attached to
|
|
201
|
+
const currentTarget = event.currentTarget as HTMLButtonElement
|
|
202
|
+
|
|
203
|
+
// Be explicit about which you need
|
|
204
|
+
if (target.tagName === 'SPAN') {
|
|
205
|
+
// Clicked on span inside button
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Reference
|
|
211
|
+
- [Vue.js TypeScript with Composition API - Event Handlers](https://vuejs.org/guide/typescript/composition-api.html#typing-event-handlers)
|
|
212
|
+
- [MDN Event Reference](https://developer.mozilla.org/en-US/docs/Web/Events)
|
|
213
|
+
- [TypeScript DOM Types](https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use Generic Argument with reactive()
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: The generic argument type differs from the actual return type due to ref unwrapping, causing type mismatches
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, typescript, reactive, ref-unwrapping, composition-api]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Do Not Use Generic Argument with reactive()
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - It is NOT recommended to use the generic argument of `reactive()` because the returned type, which handles nested ref unwrapping, is different from the generic argument type. Use interface annotation on the variable instead.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Use type annotation on the variable, not generic argument
|
|
16
|
+
- [ ] Understand that `reactive()` unwraps nested refs
|
|
17
|
+
- [ ] For generic composables, use `shallowRef` or explicit `Ref<T>` typing
|
|
18
|
+
- [ ] Prefer `ref()` for simple values to avoid these issues
|
|
19
|
+
|
|
20
|
+
## The Problem
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { reactive, ref } from 'vue'
|
|
25
|
+
|
|
26
|
+
interface Book {
|
|
27
|
+
title: string
|
|
28
|
+
year: number
|
|
29
|
+
author: Ref<string> // Nested ref
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// WRONG: Generic argument doesn't account for ref unwrapping
|
|
33
|
+
const book = reactive<Book>({
|
|
34
|
+
title: 'Vue 3 Guide',
|
|
35
|
+
year: 2024,
|
|
36
|
+
author: ref('John Doe')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// TypeScript thinks book.author is Ref<string>
|
|
40
|
+
// But at runtime, it's unwrapped to just string!
|
|
41
|
+
book.author.value // TypeScript: OK, Runtime: ERROR (author is a string, not a ref)
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## The Solution: Interface Annotation
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { reactive, ref } from 'vue'
|
|
50
|
+
|
|
51
|
+
interface Book {
|
|
52
|
+
title: string
|
|
53
|
+
year?: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// CORRECT: Annotate the variable, not the generic
|
|
57
|
+
const book: Book = reactive({
|
|
58
|
+
title: 'Vue 3 Guide'
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
book.title = 'New Title' // TypeScript knows this is string
|
|
62
|
+
book.year = 2024 // TypeScript knows this is number | undefined
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Why This Happens
|
|
67
|
+
|
|
68
|
+
When you use `reactive()`, Vue automatically unwraps any nested refs:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { reactive, ref, Ref } from 'vue'
|
|
72
|
+
|
|
73
|
+
const name = ref('John')
|
|
74
|
+
const state = reactive({
|
|
75
|
+
name: name // This is a Ref<string>
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// At runtime, state.name is 'John' (string), NOT a Ref
|
|
79
|
+
console.log(state.name) // 'John' (not ref object)
|
|
80
|
+
console.log(state.name.value) // Runtime error: .value doesn't exist
|
|
81
|
+
|
|
82
|
+
// The ACTUAL return type is different from what you'd expect
|
|
83
|
+
// reactive<{ name: Ref<string> }>() does NOT return { name: Ref<string> }
|
|
84
|
+
// It returns { name: string } due to automatic unwrapping
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Correct Patterns
|
|
88
|
+
|
|
89
|
+
### Pattern 1: Simple Interface Annotation
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<script setup lang="ts">
|
|
93
|
+
interface FormState {
|
|
94
|
+
name: string
|
|
95
|
+
email: string
|
|
96
|
+
age: number
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const form: FormState = reactive({
|
|
100
|
+
name: '',
|
|
101
|
+
email: '',
|
|
102
|
+
age: 0
|
|
103
|
+
})
|
|
104
|
+
</script>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Pattern 2: Partial for Optional Fields
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<script setup lang="ts">
|
|
111
|
+
interface User {
|
|
112
|
+
id: string
|
|
113
|
+
name: string
|
|
114
|
+
email: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Start with partial data
|
|
118
|
+
const user: Partial<User> = reactive({})
|
|
119
|
+
|
|
120
|
+
// Fill in later
|
|
121
|
+
user.id = '123'
|
|
122
|
+
user.name = 'John'
|
|
123
|
+
</script>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Pattern 3: Use ref() Instead
|
|
127
|
+
|
|
128
|
+
For simpler cases, prefer `ref()` which has more predictable typing:
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<script setup lang="ts">
|
|
132
|
+
interface User {
|
|
133
|
+
id: string
|
|
134
|
+
name: string
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ref() works well with generics
|
|
138
|
+
const user = ref<User>({
|
|
139
|
+
id: '1',
|
|
140
|
+
name: 'John'
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Access with .value - clear and predictable
|
|
144
|
+
user.value.name = 'Jane'
|
|
145
|
+
</script>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Generic Composables: Use Ref<T> or shallowRef
|
|
149
|
+
|
|
150
|
+
When working with generic type parameters in composables:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// PROBLEM: Generic T with ref() causes UnwrapRef issues
|
|
154
|
+
function useBroken<T>(initial: T) {
|
|
155
|
+
const state = ref(initial) // Type becomes Ref<UnwrapRef<T>>
|
|
156
|
+
state.value = initial // Error: T is not assignable to UnwrapRef<T>
|
|
157
|
+
return state
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// SOLUTION 1: Use explicit Ref<T> type
|
|
161
|
+
function useFixed1<T>(initial: T) {
|
|
162
|
+
const state: Ref<T> = ref(initial) as Ref<T>
|
|
163
|
+
return state
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// SOLUTION 2: Use shallowRef (no unwrapping)
|
|
167
|
+
function useFixed2<T>(initial: T) {
|
|
168
|
+
const state = shallowRef(initial) // Properly typed as ShallowRef<T>
|
|
169
|
+
return state
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## When Generic Argument IS Safe
|
|
174
|
+
|
|
175
|
+
For simple non-ref values without nested reactivity, the generic is safe:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Safe: no nested refs
|
|
179
|
+
const state = reactive<{ count: number; name: string }>({
|
|
180
|
+
count: 0,
|
|
181
|
+
name: ''
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// Also safe: explicit simple types
|
|
185
|
+
const list = reactive<string[]>([])
|
|
186
|
+
const map = reactive<Map<string, number>>(new Map())
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The issue only arises when:
|
|
190
|
+
1. You have nested Ref types in your interface
|
|
191
|
+
2. You're using generic type parameters that might contain refs
|
|
192
|
+
|
|
193
|
+
## Reference
|
|
194
|
+
- [Vue.js TypeScript with Composition API - Typing reactive()](https://vuejs.org/guide/typescript/composition-api.html#typing-reactive)
|
|
195
|
+
- [GitHub Issue: ref with generic type](https://github.com/vuejs/core/discussions/9564)
|
|
196
|
+
- [Vue TypeScript Caveats Gist](https://gist.github.com/LinusBorg/e041ff635994b50b7cec9383c3a067f1)
|