init at work
This commit is contained in:
Executable
+252
@@ -0,0 +1,252 @@
|
||||
# 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]]
|
||||
Reference in New Issue
Block a user