feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
186
skills/vue-best-practices/reference/watch-vs-watcheffect.md
Normal file
186
skills/vue-best-practices/reference/watch-vs-watcheffect.md
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
title: Choose watch vs watchEffect Based on Dependency Control Needs
|
||||
impact: MEDIUM
|
||||
impactDescription: Wrong choice leads to unnecessary re-runs or missed dependency tracking
|
||||
type: efficiency
|
||||
tags: [vue3, watch, watchEffect, watchers, reactivity, best-practices]
|
||||
---
|
||||
|
||||
# Choose watch vs watchEffect Based on Dependency Control Needs
|
||||
|
||||
**Impact: MEDIUM** - Using `watch` when `watchEffect` would be cleaner leads to repetitive code. Using `watchEffect` when `watch` is needed can cause unexpected re-runs or missed dependencies (especially with async).
|
||||
|
||||
Use `watchEffect` for simple cases where the callback uses the same state as what should trigger it. Use `watch` when you need precise control over what triggers the callback, access to old values, or lazy execution.
|
||||
|
||||
## Task Checklist
|
||||
|
||||
- [ ] Use `watchEffect` when callback logic uses the same state it should react to
|
||||
- [ ] Use `watch` when you need old value comparison
|
||||
- [ ] Use `watch` when you need lazy execution (not immediate)
|
||||
- [ ] Use `watch` for async callbacks with dependencies after await
|
||||
- [ ] Use `watch` when callback should not run on initial mount
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Feature | `watch` | `watchEffect` |
|
||||
|---------|---------|---------------|
|
||||
| Dependency tracking | Explicit (you specify) | Automatic (uses accessed properties) |
|
||||
| Lazy by default | Yes (runs only on change) | No (runs immediately) |
|
||||
| Access old value | Yes | No |
|
||||
| Async dependency tracking | Full control | Only before first await |
|
||||
| Multiple sources | Array syntax | Automatic |
|
||||
|
||||
**When to prefer `watchEffect`:**
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
const todoId = ref(1)
|
||||
const data = ref(null)
|
||||
|
||||
// GOOD: watchEffect is cleaner when callback uses same state
|
||||
watchEffect(async () => {
|
||||
const response = await fetch(
|
||||
`https://api.example.com/todos/${todoId.value}`
|
||||
)
|
||||
data.value = await response.json()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
**When to prefer `watch`:**
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const todoId = ref(1)
|
||||
const data = ref(null)
|
||||
|
||||
// BETTER with watch when:
|
||||
|
||||
// 1. You need old value
|
||||
watch(todoId, (newId, oldId) => {
|
||||
console.log(`Changed from ${oldId} to ${newId}`)
|
||||
})
|
||||
|
||||
// 2. You don't want immediate execution
|
||||
watch(todoId, () => {
|
||||
// Only runs when todoId changes, not on mount
|
||||
fetchData()
|
||||
})
|
||||
|
||||
// 3. You have dependencies after await
|
||||
watch(todoId, async (id) => {
|
||||
const response = await fetch(`/api/todos/${id}`)
|
||||
// More reactive access here still triggers correctly
|
||||
// because we explicitly specified todoId as the source
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Avoid Redundant Code with watchEffect
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watch, watchEffect } from 'vue'
|
||||
|
||||
const searchQuery = ref('')
|
||||
const category = ref('all')
|
||||
const results = ref([])
|
||||
|
||||
// BAD: Repetitive - listing same deps in source and using in callback
|
||||
watch(
|
||||
[searchQuery, category],
|
||||
([query, cat]) => {
|
||||
fetchResults(query, cat) // Same variables repeated
|
||||
}
|
||||
)
|
||||
|
||||
// GOOD: watchEffect removes repetition
|
||||
watchEffect(() => {
|
||||
fetchResults(searchQuery.value, category.value)
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Use watch for Lazy Behavior
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watch, watchEffect } from 'vue'
|
||||
|
||||
const userId = ref(null)
|
||||
|
||||
// BAD: Runs immediately even when userId is null
|
||||
watchEffect(() => {
|
||||
if (userId.value) {
|
||||
loadUserProfile(userId.value)
|
||||
}
|
||||
})
|
||||
|
||||
// GOOD: Only runs when userId actually changes
|
||||
watch(userId, (id) => {
|
||||
if (id) {
|
||||
loadUserProfile(id)
|
||||
}
|
||||
})
|
||||
|
||||
// ALSO GOOD: watch with immediate when you need both behaviors
|
||||
watch(
|
||||
userId,
|
||||
(id) => {
|
||||
if (id) loadUserProfile(id)
|
||||
},
|
||||
{ immediate: true } // Explicit about running immediately
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
## Use watch for Old Value Comparison
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const status = ref('pending')
|
||||
|
||||
// Only watch() provides old value
|
||||
watch(status, (newStatus, oldStatus) => {
|
||||
if (oldStatus === 'pending' && newStatus === 'approved') {
|
||||
showApprovalNotification()
|
||||
}
|
||||
|
||||
if (oldStatus === 'approved' && newStatus === 'rejected') {
|
||||
showRejectionWarning()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Use watch for Complex Async Dependencies
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const filters = ref({ status: 'active', sort: 'date' })
|
||||
const page = ref(1)
|
||||
const results = ref([])
|
||||
|
||||
// BETTER: watch with explicit sources for async
|
||||
// All dependencies tracked regardless of await placement
|
||||
watch(
|
||||
[filters, page],
|
||||
async ([currentFilters, currentPage]) => {
|
||||
const data = await fetchWithFilters(currentFilters)
|
||||
|
||||
// These are still correctly tracked:
|
||||
results.value = paginateResults(data, currentPage)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
## Reference
|
||||
- [Vue.js Watchers - watch vs. watchEffect](https://vuejs.org/guide/essentials/watchers.html#watch-vs-watcheffect)
|
||||
Reference in New Issue
Block a user