agy-superpowers 5.0.8 → 5.0.9
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/package.json +1 -1
- package/template/agent/rules/superpowers.md +54 -0
- package/template/agent/skills/frontend-developer/SKILL.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-nextjs.md +343 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_sections.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_template.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-event-handler-refs.md +55 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-init-once.md +42 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-use-latest.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-api-routes.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-defer-await.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-dependencies.md +51 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-parallel.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-suspense-boundaries.md +99 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-barrel-imports.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-conditional.md +31 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-defer-third-party.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-dynamic-imports.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-preload.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-event-listeners.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-localstorage-schema.md +71 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-passive-event-listeners.md +48 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-swr-dedup.md +56 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-batch-dom-css.md +107 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-function-results.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-property-access.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-storage.md +70 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-combine-iterations.md +32 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-early-exit.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-flatmap-filter.md +60 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-hoist-regexp.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-index-maps.md +37 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-length-check-first.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-min-max-loop.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-set-map-lookups.md +24 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-tosorted-immutable.md +57 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-activity.md +26 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-conditional-render.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-content-visibility.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hoist-jsx.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-no-flicker.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-suppress-warning.md +30 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-resource-hints.md +85 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-script-defer-async.md +68 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-svg-precision.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-usetransition-loading.md +75 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-defer-reads.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-dependencies.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state-no-effect.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state.md +29 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-functional-setstate.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-lazy-state-init.md +58 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo-with-default-value.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo.md +44 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-move-effect-to-event.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-no-inline-components.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-simple-expression-in-memo.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-split-combined-hooks.md +64 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-transitions.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-deferred-value.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-ref-transient-values.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-after-nonblocking.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-auth-actions.md +96 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-lru.md +41 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-react.md +76 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-dedup-props.md +65 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-hoist-static-io.md +142 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-parallel-fetching.md +83 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-serialization.md +38 -0
- package/template/agent/skills/frontend-developer/references/svelte-sveltekit.md +220 -0
- package/template/agent/skills/frontend-developer/references/vanilla.md +275 -0
- package/template/agent/skills/frontend-developer/references/vue-nuxt.md +289 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/_index.md +154 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-class-based-technique.md +254 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-state-driven-technique.md +291 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-async.md +97 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-data-flow.md +307 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-fallthrough-attrs.md +174 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-keep-alive.md +137 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-slots.md +216 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-suspense.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-teleport.md +108 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition-group.md +128 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition.md +125 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/composables.md +290 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/directives.md +162 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-v-once-v-memo-directives.md +182 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-virtualize-large-lists.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/plugins.md +166 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/reactivity.md +344 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/render-functions.md +201 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/sfc.md +310 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/state-management.md +135 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/updated-hook-performance.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/_index.md +23 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforeenter-no-param-trigger.md +167 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforerouteenter-no-this.md +176 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-guard-async-await-pattern.md +227 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-infinite-loop.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-next-deprecated.md +150 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-param-change-no-lifecycle.md +181 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-simple-routing-cleanup.md +209 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-use-vue-router-for-production.md +183 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/_index.md +29 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/async-component-testing.md +163 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/teleport-testing-complexity.md +158 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-async-await-flushpromises.md +175 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-browser-vs-node-runners.md +208 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-component-blackbox-approach.md +144 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-composables-helper-wrapper.md +238 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-e2e-playwright-recommended.md +242 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-no-snapshot-only.md +197 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-pinia-store-setup.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-suspense-async-components.md +229 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-vitest-recommended-for-vue.md +204 -0
- package/template/agent/skills/mobile-developer/SKILL.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-native.md +396 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-accessibility.md +36 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-architecture.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-coroutines.md +139 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-data-layer.md +51 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-emulator-skill.md +108 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-gradle-logic.md +126 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-retrofit.md +142 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-testing.md +102 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-viewmodel.md +43 -0
- package/template/agent/skills/mobile-developer/references/android-rules/coil-compose.md +74 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-navigation.md +422 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-performance-audit.md +199 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-ui.md +49 -0
- package/template/agent/skills/mobile-developer/references/android-rules/gradle-build-performance.md +346 -0
- package/template/agent/skills/mobile-developer/references/android-rules/kotlin-concurrency-expert.md +169 -0
- package/template/agent/skills/mobile-developer/references/android-rules/rxjava-to-coroutines-migration.md +101 -0
- package/template/agent/skills/mobile-developer/references/android-rules/xml-to-compose-migration.md +338 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-best-practices.md +52 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-checks-migration.md +134 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-cli-app-best-practices.md +123 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-doc-validation.md +45 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-matcher-best-practices.md +106 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-modern-features.md +241 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-package-maintenance.md +75 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-test-fundamentals.md +124 -0
- package/template/agent/skills/mobile-developer/references/flutter.md +291 -0
- package/template/agent/skills/mobile-developer/references/ios-native.md +358 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/accessibility-patterns.md +215 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-advanced.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-basics.md +284 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-transitions.md +326 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts-accessibility.md +135 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts.md +602 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/image-optimization.md +203 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/latest-apis.md +464 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/layout-best-practices.md +266 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/liquid-glass.md +416 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/list-patterns.md +394 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-scenes.md +318 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-views.md +357 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-window-styling.md +303 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/performance-patterns.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/scroll-patterns.md +293 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/sheet-navigation-patterns.md +363 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/state-management.md +417 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/view-structure.md +389 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_sections.md +86 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_template.md +28 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-derived-value.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gesture-detector-press.md +95 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gpu-properties.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/design-system-compound-components.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/fonts-config-plugin.md +71 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/imports-design-system-folder.md +68 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/js-hoist-intl.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-callbacks.md +44 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-function-references.md +132 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-images.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-inline-objects.md +97 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-expensive.md +94 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-memo.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-types.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-virtualize.md +67 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-native-deps-in-app.md +46 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-single-dependency-versions.md +63 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/navigation-native-navigators.md +188 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-destructure-functions.md +50 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-reanimated-shared-values.md +48 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-dispatcher.md +91 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-fallback.md +56 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-minimize.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-no-falsy-and.md +74 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-text-in-text-component.md +36 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/scroll-position-no-state.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/state-ground-truth.md +80 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-expo-image.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-image-gallery.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-measure-views.md +78 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-menus.md +174 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-native-modals.md +77 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-pressable.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-safe-area-scroll.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-scrollview-content-inset.md +45 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-styling.md +87 -0
- package/template/agent/skills/mobile-developer/references/react-native.md +345 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: State-driven Animations with CSS Transitions and Style Bindings
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Combining Vue's reactive style bindings with CSS transitions creates smooth, interactive animations
|
|
5
|
+
type: best-practice
|
|
6
|
+
tags: [vue3, animation, css, transition, style-binding, state, interactive]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# State-driven Animations with CSS Transitions and Style Bindings
|
|
10
|
+
|
|
11
|
+
**Impact: LOW** - For responsive, interactive animations that react to user input or state changes, combine Vue's dynamic style bindings with CSS transitions. This creates smooth animations that interpolate values in real-time based on state.
|
|
12
|
+
|
|
13
|
+
## Task List
|
|
14
|
+
|
|
15
|
+
- Use `:style` binding for dynamic properties that change frequently
|
|
16
|
+
- Add CSS `transition` property to smoothly animate between values
|
|
17
|
+
- Consider using `transform` and `opacity` for GPU-accelerated animations
|
|
18
|
+
- For complex value interpolation, use watchers with animation libraries
|
|
19
|
+
|
|
20
|
+
## Basic Pattern
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
@mousemove="onMousemove"
|
|
26
|
+
:style="{ backgroundColor: `hsl(${hue}, 80%, 50%)` }"
|
|
27
|
+
class="interactive-area"
|
|
28
|
+
>
|
|
29
|
+
<p>Move your mouse across this div...</p>
|
|
30
|
+
<p>Hue: {{ hue }}</p>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
import { ref } from 'vue'
|
|
36
|
+
|
|
37
|
+
const hue = ref(0)
|
|
38
|
+
|
|
39
|
+
function onMousemove(e) {
|
|
40
|
+
// Map mouse X position to hue (0-360)
|
|
41
|
+
const rect = e.currentTarget.getBoundingClientRect()
|
|
42
|
+
hue.value = Math.round((e.clientX - rect.left) / rect.width * 360)
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style>
|
|
47
|
+
.interactive-area {
|
|
48
|
+
transition: background-color 0.3s ease;
|
|
49
|
+
height: 200px;
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Common Use Cases
|
|
59
|
+
|
|
60
|
+
### Following Mouse Position
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<template>
|
|
64
|
+
<div
|
|
65
|
+
class="container"
|
|
66
|
+
@mousemove="onMousemove"
|
|
67
|
+
>
|
|
68
|
+
<div
|
|
69
|
+
class="follower"
|
|
70
|
+
:style="{
|
|
71
|
+
transform: `translate(${x}px, ${y}px)`
|
|
72
|
+
}"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<script setup>
|
|
78
|
+
import { ref } from 'vue'
|
|
79
|
+
|
|
80
|
+
const x = ref(0)
|
|
81
|
+
const y = ref(0)
|
|
82
|
+
|
|
83
|
+
function onMousemove(e) {
|
|
84
|
+
const rect = e.currentTarget.getBoundingClientRect()
|
|
85
|
+
x.value = e.clientX - rect.left
|
|
86
|
+
y.value = e.clientY - rect.top
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<style>
|
|
91
|
+
.container {
|
|
92
|
+
position: relative;
|
|
93
|
+
height: 300px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.follower {
|
|
97
|
+
position: absolute;
|
|
98
|
+
width: 20px;
|
|
99
|
+
height: 20px;
|
|
100
|
+
background: blue;
|
|
101
|
+
border-radius: 50%;
|
|
102
|
+
/* Smooth following with transition */
|
|
103
|
+
transition: transform 0.1s ease-out;
|
|
104
|
+
/* Prevent the follower from triggering mousemove */
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Progress Animation
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<template>
|
|
114
|
+
<div class="progress-container">
|
|
115
|
+
<div
|
|
116
|
+
class="progress-bar"
|
|
117
|
+
:style="{ width: `${progress}%` }"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
<input
|
|
121
|
+
type="range"
|
|
122
|
+
v-model.number="progress"
|
|
123
|
+
min="0"
|
|
124
|
+
max="100"
|
|
125
|
+
/>
|
|
126
|
+
</template>
|
|
127
|
+
|
|
128
|
+
<script setup>
|
|
129
|
+
import { ref } from 'vue'
|
|
130
|
+
|
|
131
|
+
const progress = ref(0)
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<style>
|
|
135
|
+
.progress-container {
|
|
136
|
+
height: 20px;
|
|
137
|
+
background: #e0e0e0;
|
|
138
|
+
border-radius: 10px;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.progress-bar {
|
|
143
|
+
height: 100%;
|
|
144
|
+
background: linear-gradient(90deg, #4CAF50, #8BC34A);
|
|
145
|
+
transition: width 0.3s ease;
|
|
146
|
+
}
|
|
147
|
+
</style>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Scroll-based Animation
|
|
151
|
+
|
|
152
|
+
```vue
|
|
153
|
+
<template>
|
|
154
|
+
<div
|
|
155
|
+
class="hero"
|
|
156
|
+
:style="{
|
|
157
|
+
opacity: heroOpacity,
|
|
158
|
+
transform: `translateY(${scrollOffset}px)`
|
|
159
|
+
}"
|
|
160
|
+
>
|
|
161
|
+
<h1>Scroll Down</h1>
|
|
162
|
+
</div>
|
|
163
|
+
</template>
|
|
164
|
+
|
|
165
|
+
<script setup>
|
|
166
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
167
|
+
|
|
168
|
+
const scrollY = ref(0)
|
|
169
|
+
|
|
170
|
+
const heroOpacity = computed(() => {
|
|
171
|
+
return Math.max(0, 1 - scrollY.value / 300)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const scrollOffset = computed(() => {
|
|
175
|
+
return scrollY.value * 0.5 // Parallax effect
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
function handleScroll() {
|
|
179
|
+
scrollY.value = window.scrollY
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
onMounted(() => {
|
|
183
|
+
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
onUnmounted(() => {
|
|
187
|
+
window.removeEventListener('scroll', handleScroll)
|
|
188
|
+
})
|
|
189
|
+
</script>
|
|
190
|
+
|
|
191
|
+
<style>
|
|
192
|
+
.hero {
|
|
193
|
+
height: 100vh;
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
justify-content: center;
|
|
197
|
+
/* Note: No transition for scroll-based animations - they should be instant */
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Color Theme Transition
|
|
203
|
+
|
|
204
|
+
```vue
|
|
205
|
+
<template>
|
|
206
|
+
<div
|
|
207
|
+
class="app"
|
|
208
|
+
:style="themeStyles"
|
|
209
|
+
>
|
|
210
|
+
<button @click="toggleTheme">Toggle Theme</button>
|
|
211
|
+
<p>Current theme: {{ isDark ? 'Dark' : 'Light' }}</p>
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|
|
214
|
+
|
|
215
|
+
<script setup>
|
|
216
|
+
import { ref, computed } from 'vue'
|
|
217
|
+
|
|
218
|
+
const isDark = ref(false)
|
|
219
|
+
|
|
220
|
+
const themeStyles = computed(() => ({
|
|
221
|
+
'--bg-color': isDark.value ? '#1a1a1a' : '#ffffff',
|
|
222
|
+
'--text-color': isDark.value ? '#ffffff' : '#1a1a1a',
|
|
223
|
+
backgroundColor: 'var(--bg-color)',
|
|
224
|
+
color: 'var(--text-color)'
|
|
225
|
+
}))
|
|
226
|
+
|
|
227
|
+
function toggleTheme() {
|
|
228
|
+
isDark.value = !isDark.value
|
|
229
|
+
}
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<style>
|
|
233
|
+
.app {
|
|
234
|
+
min-height: 100vh;
|
|
235
|
+
transition: background-color 0.5s ease, color 0.5s ease;
|
|
236
|
+
}
|
|
237
|
+
</style>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Advanced: Numerical Tweening with Watchers
|
|
241
|
+
|
|
242
|
+
For smooth number animations (counters, stats), use watchers with animation libraries:
|
|
243
|
+
|
|
244
|
+
```vue
|
|
245
|
+
<template>
|
|
246
|
+
<div>
|
|
247
|
+
<input v-model.number="targetNumber" type="number" />
|
|
248
|
+
<p class="counter">{{ displayNumber.toFixed(0) }}</p>
|
|
249
|
+
</div>
|
|
250
|
+
</template>
|
|
251
|
+
|
|
252
|
+
<script setup>
|
|
253
|
+
import { computed, ref, reactive, watch } from 'vue'
|
|
254
|
+
import gsap from 'gsap'
|
|
255
|
+
|
|
256
|
+
const targetNumber = ref(0)
|
|
257
|
+
const tweened = reactive({ value: 0 })
|
|
258
|
+
|
|
259
|
+
// Computed for display
|
|
260
|
+
const displayNumber = computed(() => tweened.value)
|
|
261
|
+
|
|
262
|
+
watch(targetNumber, (newValue) => {
|
|
263
|
+
gsap.to(tweened, {
|
|
264
|
+
duration: 0.5,
|
|
265
|
+
value: Number(newValue) || 0,
|
|
266
|
+
ease: 'power2.out'
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
</script>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Performance Considerations
|
|
273
|
+
|
|
274
|
+
```vue
|
|
275
|
+
<style>
|
|
276
|
+
/* GOOD: GPU-accelerated properties */
|
|
277
|
+
.element {
|
|
278
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* AVOID: Properties that trigger layout recalculation */
|
|
282
|
+
.element {
|
|
283
|
+
transition: width 0.3s ease, height 0.3s ease, margin 0.3s ease;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* For high-frequency updates, consider will-change */
|
|
287
|
+
.frequently-animated {
|
|
288
|
+
will-change: transform;
|
|
289
|
+
}
|
|
290
|
+
</style>
|
|
291
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Async Component Best Practices
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Poor async component strategy can delay interactivity in SSR apps and create loading UI flicker
|
|
5
|
+
type: best-practice
|
|
6
|
+
tags: [vue3, async-components, ssr, hydration, performance, ux]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Async Component Best Practices
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Async components should reduce JavaScript cost without degrading perceived performance. Focus on hydration timing in SSR and stable loading UX.
|
|
12
|
+
|
|
13
|
+
## Task List
|
|
14
|
+
|
|
15
|
+
- Use lazy hydration strategies for non-critical SSR component trees
|
|
16
|
+
- Import only the hydration helpers you actually use
|
|
17
|
+
- Keep `loadingComponent` delay near the default `200ms` unless real UX data suggests otherwise
|
|
18
|
+
- Configure `delay` and `timeout` together for predictable loading behavior
|
|
19
|
+
|
|
20
|
+
## Use Lazy Hydration Strategies in SSR
|
|
21
|
+
|
|
22
|
+
In Vue 3.5+, async components can delay hydration until idle time, visibility, media query match, or user interaction.
|
|
23
|
+
|
|
24
|
+
**BAD:**
|
|
25
|
+
```vue
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { defineAsyncComponent } from 'vue'
|
|
28
|
+
|
|
29
|
+
const AsyncComments = defineAsyncComponent({
|
|
30
|
+
loader: () => import('./Comments.vue')
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**GOOD:**
|
|
36
|
+
```vue
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import {
|
|
39
|
+
defineAsyncComponent,
|
|
40
|
+
hydrateOnVisible,
|
|
41
|
+
hydrateOnIdle
|
|
42
|
+
} from 'vue'
|
|
43
|
+
|
|
44
|
+
const AsyncComments = defineAsyncComponent({
|
|
45
|
+
loader: () => import('./Comments.vue'),
|
|
46
|
+
hydrate: hydrateOnVisible({ rootMargin: '100px' })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const AsyncFooter = defineAsyncComponent({
|
|
50
|
+
loader: () => import('./Footer.vue'),
|
|
51
|
+
hydrate: hydrateOnIdle(5000)
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Prevent Loading Spinner Flicker
|
|
57
|
+
|
|
58
|
+
Avoid showing loading UI immediately for components that usually resolve quickly.
|
|
59
|
+
|
|
60
|
+
**BAD:**
|
|
61
|
+
```vue
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { defineAsyncComponent } from 'vue'
|
|
64
|
+
import LoadingSpinner from './LoadingSpinner.vue'
|
|
65
|
+
|
|
66
|
+
const AsyncDashboard = defineAsyncComponent({
|
|
67
|
+
loader: () => import('./Dashboard.vue'),
|
|
68
|
+
loadingComponent: LoadingSpinner,
|
|
69
|
+
delay: 0
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**GOOD:**
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import { defineAsyncComponent } from 'vue'
|
|
78
|
+
import LoadingSpinner from './LoadingSpinner.vue'
|
|
79
|
+
import ErrorDisplay from './ErrorDisplay.vue'
|
|
80
|
+
|
|
81
|
+
const AsyncDashboard = defineAsyncComponent({
|
|
82
|
+
loader: () => import('./Dashboard.vue'),
|
|
83
|
+
loadingComponent: LoadingSpinner,
|
|
84
|
+
errorComponent: ErrorDisplay,
|
|
85
|
+
delay: 200,
|
|
86
|
+
timeout: 30000
|
|
87
|
+
})
|
|
88
|
+
</script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Delay Guidelines
|
|
92
|
+
|
|
93
|
+
| Scenario | Recommended Delay |
|
|
94
|
+
|----------|-------------------|
|
|
95
|
+
| Small component, fast network | `200ms` |
|
|
96
|
+
| Known heavy component | `100ms` |
|
|
97
|
+
| Background or non-critical UI | `300-500ms` |
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Component Data Flow Best Practices
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Clear data flow between components prevents state bugs, stale UI, and brittle coupling
|
|
5
|
+
type: best-practice
|
|
6
|
+
tags: [vue3, props, emits, v-model, provide-inject, data-flow, typescript]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Component Data Flow Best Practices
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Vue components stay reliable when data flow is explicit: props go down, events go up, `v-model` handles two-way bindings, and provide/inject supports cross-tree dependencies. Blurring these boundaries leads to stale state, hidden coupling, and hard-to-debug UI.
|
|
12
|
+
|
|
13
|
+
The main principle of data flow in Vue.js is **Props Down / Events Up**. This is the most maintainable default, and one-way flow scales well.
|
|
14
|
+
|
|
15
|
+
## Task List
|
|
16
|
+
|
|
17
|
+
- Treat props as read-only inputs
|
|
18
|
+
- Use props/emit for component communication; reserve refs for imperative actions
|
|
19
|
+
- When refs are required for imperative APIs, type them with template refs
|
|
20
|
+
- Emit events instead of mutating parent state directly
|
|
21
|
+
- Use `defineModel` for v-model in modern Vue (3.4+)
|
|
22
|
+
- Handle v-model modifiers deliberately in child components
|
|
23
|
+
- Use symbols for provide/inject keys to avoid props drilling (over ~3 layers)
|
|
24
|
+
- Keep mutations in the provider or expose explicit actions
|
|
25
|
+
- In TypeScript projects, prefer type-based `defineProps`, `defineEmits`, and `InjectionKey`
|
|
26
|
+
|
|
27
|
+
## Props: One-Way Data Down
|
|
28
|
+
|
|
29
|
+
Props are inputs. Do not mutate them in the child.
|
|
30
|
+
|
|
31
|
+
**BAD:**
|
|
32
|
+
```vue
|
|
33
|
+
<script setup>
|
|
34
|
+
const props = defineProps({ count: Number })
|
|
35
|
+
|
|
36
|
+
function increment() {
|
|
37
|
+
props.count++
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**GOOD:**
|
|
43
|
+
|
|
44
|
+
If state needs to change, emit an event, use `v-model` or create a local copy.
|
|
45
|
+
|
|
46
|
+
## Prefer props/emit over component refs
|
|
47
|
+
|
|
48
|
+
**BAD:**
|
|
49
|
+
```vue
|
|
50
|
+
<script setup>
|
|
51
|
+
import { ref } from 'vue'
|
|
52
|
+
import UserForm from './UserForm.vue'
|
|
53
|
+
|
|
54
|
+
const formRef = ref(null)
|
|
55
|
+
|
|
56
|
+
function submitForm() {
|
|
57
|
+
if (formRef.value.isValid) {
|
|
58
|
+
formRef.value.submit()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<UserForm ref="formRef" />
|
|
65
|
+
<button @click="submitForm">Submit</button>
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**GOOD:**
|
|
70
|
+
```vue
|
|
71
|
+
<script setup>
|
|
72
|
+
import UserForm from './UserForm.vue'
|
|
73
|
+
|
|
74
|
+
function handleSubmit(formData) {
|
|
75
|
+
api.submit(formData)
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<UserForm @submit="handleSubmit" />
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Type component refs when imperative access is required
|
|
85
|
+
|
|
86
|
+
Prefer props/emits by default. When a parent must call an exposed child method, type the ref explicitly and expose only the intended API from the child with `defineExpose`.
|
|
87
|
+
|
|
88
|
+
**BAD:**
|
|
89
|
+
```vue
|
|
90
|
+
<script setup lang="ts">
|
|
91
|
+
import { ref, onMounted } from 'vue'
|
|
92
|
+
import DialogPanel from './DialogPanel.vue'
|
|
93
|
+
|
|
94
|
+
const panelRef = ref(null)
|
|
95
|
+
|
|
96
|
+
onMounted(() => {
|
|
97
|
+
panelRef.value.open()
|
|
98
|
+
})
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<template>
|
|
102
|
+
<DialogPanel ref="panelRef" />
|
|
103
|
+
</template>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**GOOD:**
|
|
107
|
+
```vue
|
|
108
|
+
<!-- DialogPanel.vue -->
|
|
109
|
+
<script setup lang="ts">
|
|
110
|
+
function open() {}
|
|
111
|
+
|
|
112
|
+
defineExpose({ open })
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```vue
|
|
117
|
+
<!-- Parent.vue -->
|
|
118
|
+
<script setup lang="ts">
|
|
119
|
+
import { onMounted, useTemplateRef } from 'vue'
|
|
120
|
+
import DialogPanel from './DialogPanel.vue'
|
|
121
|
+
|
|
122
|
+
// Vue 3.5+ with useTemplateRef
|
|
123
|
+
const panelRef = useTemplateRef('panelRef')
|
|
124
|
+
|
|
125
|
+
// Before Vue 3.5 with manual typing and ref
|
|
126
|
+
// const panelRef = ref<InstanceType<typeof DialogPanel> | null>(null)
|
|
127
|
+
|
|
128
|
+
onMounted(() => {
|
|
129
|
+
panelRef.value?.open()
|
|
130
|
+
})
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<template>
|
|
134
|
+
<DialogPanel ref="panelRef" />
|
|
135
|
+
</template>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Emits: Explicit Events Up
|
|
139
|
+
|
|
140
|
+
Component events do not bubble. If a parent needs to know about an event, re-emit it explicitly.
|
|
141
|
+
|
|
142
|
+
**BAD:**
|
|
143
|
+
```vue
|
|
144
|
+
<!-- Parent expects "saved" from grandchild, but it won't bubble -->
|
|
145
|
+
<Child @saved="onSaved" />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**GOOD:**
|
|
149
|
+
```vue
|
|
150
|
+
<!-- Child.vue -->
|
|
151
|
+
<script setup>
|
|
152
|
+
const emit = defineEmits(['saved'])
|
|
153
|
+
|
|
154
|
+
function onGrandchildSaved(payload) {
|
|
155
|
+
emit('saved', payload)
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<template>
|
|
160
|
+
<Grandchild @saved="onGrandchildSaved" />
|
|
161
|
+
</template>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Event naming:** use kebab-case in templates and camelCase in script:
|
|
165
|
+
```vue
|
|
166
|
+
<script setup>
|
|
167
|
+
const emit = defineEmits(['updateUser'])
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<template>
|
|
171
|
+
<ProfileForm @update-user="emit('updateUser', $event)" />
|
|
172
|
+
</template>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## `v-model`: Predictable Two-Way Bindings
|
|
176
|
+
|
|
177
|
+
Use `defineModel` by default for component bindings and emit updates on input. Only use the `modelValue` + `update:modelValue` pattern if you are on Vue < 3.4.
|
|
178
|
+
|
|
179
|
+
**BAD:**
|
|
180
|
+
```vue
|
|
181
|
+
<script setup>
|
|
182
|
+
const props = defineProps({ value: String })
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<template>
|
|
186
|
+
<input :value="props.value" @input="$emit('input', $event.target.value)" />
|
|
187
|
+
</template>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**GOOD (Vue 3.4+):**
|
|
191
|
+
```vue
|
|
192
|
+
<script setup>
|
|
193
|
+
const model = defineModel({ type: String })
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<template>
|
|
197
|
+
<input v-model="model" />
|
|
198
|
+
</template>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**GOOD (Vue < 3.4):**
|
|
202
|
+
```vue
|
|
203
|
+
<script setup>
|
|
204
|
+
const props = defineProps({ modelValue: String })
|
|
205
|
+
const emit = defineEmits(['update:modelValue'])
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<template>
|
|
209
|
+
<input
|
|
210
|
+
:value="props.modelValue"
|
|
211
|
+
@input="emit('update:modelValue', $event.target.value)"
|
|
212
|
+
/>
|
|
213
|
+
</template>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
If you need the updated value immediately after a change, use the input event value or `nextTick` in the parent.
|
|
217
|
+
|
|
218
|
+
## Provide/Inject: Shared Context Without Prop Drilling
|
|
219
|
+
|
|
220
|
+
Use provide/inject for cross-tree state, but keep mutations centralized in the provider and expose explicit actions.
|
|
221
|
+
|
|
222
|
+
**BAD:**
|
|
223
|
+
```vue
|
|
224
|
+
// Provider.vue
|
|
225
|
+
provide('theme', reactive({ dark: false }))
|
|
226
|
+
|
|
227
|
+
// Consumer.vue
|
|
228
|
+
const theme = inject('theme')
|
|
229
|
+
// Mutating shared state from any depth becomes hard to track
|
|
230
|
+
theme.dark = true
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**GOOD:**
|
|
234
|
+
```vue
|
|
235
|
+
// Provider.vue
|
|
236
|
+
const theme = reactive({ dark: false })
|
|
237
|
+
const toggleTheme = () => { theme.dark = !theme.dark }
|
|
238
|
+
|
|
239
|
+
provide(themeKey, readonly(theme))
|
|
240
|
+
provide(themeActionsKey, { toggleTheme })
|
|
241
|
+
|
|
242
|
+
// Consumer.vue
|
|
243
|
+
const theme = inject(themeKey)
|
|
244
|
+
const { toggleTheme } = inject(themeActionsKey)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Use symbols for keys to avoid collisions in large apps:
|
|
248
|
+
```ts
|
|
249
|
+
export const themeKey = Symbol('theme')
|
|
250
|
+
export const themeActionsKey = Symbol('theme-actions')
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Use TypeScript Contracts for Public Component APIs
|
|
254
|
+
|
|
255
|
+
In TypeScript projects, type component boundaries directly with `defineProps`, `defineEmits`, and `InjectionKey` so invalid payloads and mismatched injections fail at compile time.
|
|
256
|
+
|
|
257
|
+
**BAD:**
|
|
258
|
+
```vue
|
|
259
|
+
<script setup lang="ts">
|
|
260
|
+
import { inject } from 'vue'
|
|
261
|
+
|
|
262
|
+
const props = defineProps({
|
|
263
|
+
userId: String
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const emit = defineEmits(['save'])
|
|
267
|
+
const settings = inject('settings')
|
|
268
|
+
|
|
269
|
+
// Payload shape is not checked here
|
|
270
|
+
emit('save', 123)
|
|
271
|
+
|
|
272
|
+
// Key is string-based and not type-safe
|
|
273
|
+
settings?.theme = 'dark'
|
|
274
|
+
</script>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**GOOD:**
|
|
278
|
+
```vue
|
|
279
|
+
<script setup lang="ts">
|
|
280
|
+
import { inject, provide } from 'vue'
|
|
281
|
+
import type { InjectionKey } from 'vue'
|
|
282
|
+
|
|
283
|
+
interface Props {
|
|
284
|
+
userId: string
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
interface Emits {
|
|
288
|
+
save: [payload: { id: string; draft: boolean }]
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface Settings {
|
|
292
|
+
theme: 'light' | 'dark'
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const settingsKey: InjectionKey<Settings> = Symbol('settings')
|
|
296
|
+
|
|
297
|
+
const props = defineProps<Props>()
|
|
298
|
+
const emit = defineEmits<Emits>()
|
|
299
|
+
|
|
300
|
+
provide(settingsKey, { theme: 'light' })
|
|
301
|
+
|
|
302
|
+
const settings = inject(settingsKey)
|
|
303
|
+
if (settings) {
|
|
304
|
+
emit('save', { id: props.userId, draft: false })
|
|
305
|
+
}
|
|
306
|
+
</script>
|
|
307
|
+
```
|