Files
it-know-how/vue/01-Refs, Computed, and Reactivity.md
2026-03-27 12:58:01 +01:00

253 lines
4.6 KiB
Markdown
Executable File

# Refs, Computed, and Reactivity
This note explains the most important Vue state concepts for a Python developer.
## `ref`: reactive storage for one value
A `ref` wraps a value so Vue can track it.
```ts
import { ref } from 'vue';
const count = ref(0);
const username = ref('mathias');
const isLoading = ref(false);
```
In script code, you read and write using `.value`.
```ts
count.value += 1;
username.value = 'new name';
isLoading.value = true;
```
In templates, Vue unwraps refs automatically.
```vue
<template>
<p>{{ count }}</p>
</template>
```
You write `count`, not `count.value`, in the template.
## Python comparison
Python:
```python
count = 0
count += 1
```
Vue script:
```ts
const count = ref(0);
count.value += 1;
```
The extra `.value` exists because `count` is a reactive wrapper object, not the raw number.
## Example: loading state
```vue
<template>
<button :disabled="isLoading" @click="save">
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isLoading = ref(false);
async function save() {
isLoading.value = true;
try {
await fakeRequest();
} finally {
isLoading.value = false;
}
}
function fakeRequest() {
return new Promise((resolve) => setTimeout(resolve, 1000));
}
</script>
```
The button label and disabled state both react to `isLoading`.
## `computed`: derived state
A `computed` value is calculated from other reactive values.
```ts
import { ref, computed } from 'vue';
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
```
Use `computed` when a value can be derived instead of stored manually.
Bad pattern:
```ts
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = ref('Ada Lovelace');
```
Now you must remember to update `fullName` yourself every time.
Better:
```ts
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
```
## Python comparison
This is similar to a property.
```python
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
```
Vue `computed` plays a similar role.
## Real example from your app
In FlexibilityTable, this pattern appears:
```ts
const hasValidPeriodRange = computed(() => {
if (!selectedPeriodStartFrom.value || !selectedPeriodStartUntil.value) {
return true;
}
return new Date(selectedPeriodStartFrom.value).getTime() <= new Date(selectedPeriodStartUntil.value).getTime();
});
```
This means:
- if one boundary is missing, the range is treated as valid
- if both are set, start must be before or equal to end
The component does not store `hasValidPeriodRange` manually. It derives it from the two selected dates.
## Another example: filtered list
```ts
const searchText = ref('');
const users = ref(['Alice', 'Bob', 'Charlie']);
const filteredUsers = computed(() => {
return users.value.filter((user) =>
user.toLowerCase().includes(searchText.value.toLowerCase()),
);
});
```
If `searchText` changes, `filteredUsers` updates automatically.
## Rule of Thumb
Use `ref` when:
- the value changes over time
- the UI should react to that change
- the value is primary state
Use `computed` when:
- the value is calculated from other reactive values
- you do not want to duplicate state
## Common Beginner Mistakes
### forgetting `.value` in script
Wrong:
```ts
count += 1;
```
Right:
```ts
count.value += 1;
```
### using `computed` for side effects
Bad:
```ts
const result = computed(() => {
console.log('side effect');
return count.value * 2;
});
```
A computed should mainly calculate and return a value.
### storing what can be derived
Bad:
```ts
const selectedFirst = ref('2026-01-01');
const selectedSecond = ref('2026-01-10');
const isValid = ref(true);
```
Better:
```ts
const isValid = computed(() => selectedFirst.value <= selectedSecond.value);
```
## Tiny Exercise
What should be `ref` and what should be `computed`?
Scenario:
- user types first name
- user types last name
- screen shows full name
- submit button disabled when first name is empty
Answer:
```ts
const firstName = ref('');
const lastName = ref('');
const fullName = computed(() => `${firstName.value} ${lastName.value}`.trim());
const isSubmitDisabled = computed(() => !firstName.value.trim());
```
## Related Notes
- [[00-Vue for Python Developers]]
- [[02-Templates, v-model, and Events]]
- [[04-FlexibilityTable Filter Flow]]