4.3 KiB
4.3 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Ensure All Dependencies Are Accessed in Computed Properties | HIGH | Conditional logic can prevent dependency tracking causing stale computed values | capability |
|
Ensure All Dependencies Are Accessed in Computed Properties
Impact: HIGH - Vue tracks computed property dependencies by monitoring which reactive properties are accessed during execution. If conditional logic prevents a property from being accessed on the first run, Vue won't track it as a dependency, causing the computed property to not update when that property changes.
This is a subtle but common source of bugs, especially with short-circuit evaluation (&&, ||) and early returns.
Task Checklist
- Access all reactive dependencies before any conditional logic
- Be cautious with short-circuit operators (
&&,||) that may skip property access - Store all dependencies in variables at the start of the computed getter
- Test computed properties with different initial states
Incorrect:
<script setup>
import { ref, computed } from 'vue';
const isEnabled = ref(false);
const data = ref('important data');
// BAD: If isEnabled is false initially, data.value is never accessed
// Vue won't track 'data' as a dependency!
const result = computed(() => {
if (!isEnabled.value) {
return 'disabled';
}
return data.value; // This dependency may not be tracked
});
// BAD: Short-circuit prevents second access
const password = ref('');
const confirmPassword = ref('');
const isValid = computed(() => {
// If password is empty, confirmPassword is never accessed
return password.value && password.value === confirmPassword.value;
});
// BAD: Early return prevents dependency access
const user = ref(null);
const permissions = ref(['read', 'write']);
const canEdit = computed(() => {
if (!user.value) {
return false; // permissions.value never accessed when user is null
}
return permissions.value.includes('write');
});
</script>
Correct:
<script setup>
import { ref, computed } from 'vue';
const isEnabled = ref(false);
const data = ref('important data');
// GOOD: Access all dependencies first
const result = computed(() => {
const enabled = isEnabled.value;
const currentData = data.value; // Always accessed
if (!enabled) {
return 'disabled';
}
return currentData;
});
// GOOD: Access both values before comparison
const password = ref('');
const confirmPassword = ref('');
const isValid = computed(() => {
const pwd = password.value;
const confirm = confirmPassword.value; // Always accessed
return pwd && pwd === confirm;
});
// GOOD: Access all reactive sources upfront
const user = ref(null);
const permissions = ref(['read', 'write']);
const canEdit = computed(() => {
const currentUser = user.value;
const currentPermissions = permissions.value; // Always accessed
if (!currentUser) {
return false;
}
return currentPermissions.includes('write');
});
</script>
The Dependency Tracking Mechanism
Vue's reactivity system works by tracking which reactive properties are accessed when a computed property runs:
// How Vue tracks dependencies (simplified):
// 1. Start tracking
// 2. Run the getter function
// 3. Record every .value or reactive property access
// 4. Stop tracking
const computed = computed(() => {
// Vue starts tracking here
if (conditionA.value) {
// conditionA is tracked
return valueB.value; // valueB is ONLY tracked if conditionA is true
}
return 'default'; // If conditionA is false, valueB is NOT tracked!
});
Pattern: Destructure All Dependencies First
// GOOD PATTERN: Destructure/access everything at the top
const result = computed(() => {
// Access all potential dependencies
const { user, settings, items } = toRefs(store);
const userVal = user.value;
const settingsVal = settings.value;
const itemsVal = items.value;
// Now use conditional logic safely
if (!userVal) return [];
if (!settingsVal.enabled) return [];
return itemsVal.filter((i) => i.active);
});