Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Composables - Wielokrotne Użycie Logiki

Dr. Nova wyjaśnia: "W NOVA LAB wiele modułów potrzebuje tych samych systemów - timer misji, odczyty sensorów, połączenie z bazą danych. Zamiast kopiować ten sam kod do każdego modułu, tworzymy współdzielone systemy. W Vue takim systemem jest composable."

Czym Jest Composable?

Composable to funkcja, która enkapsuluje i zwraca reaktywny stan oraz logikę. Pozwala współdzielić logikę między wieloma komponentami bez duplikowania kodu.

Konwencja nazewnictwa: composable zawsze zaczyna się od

use
- np.
useTimer
,
useCounter
,
useMarsData
.

1// composables/useCounter.js
2import { ref } from 'vue'
3
4export function useCounter(initialValue = 0) {
5  const count = ref(initialValue)
6
7  function increment() {
8    count.value++
9  }
10
11  function decrement() {
12    count.value--
13  }
14
15  function reset() {
16    count.value = initialValue
17  }
18
19  // Zwracamy reaktywny stan i funkcje
20  return { count, increment, decrement, reset }
21}

Używanie Composable w Komponencie

1<template>
2  <div style="background: #0a0e27; color: #00ff88; padding: 1rem;">
3    <h3>Licznik modułów: {{ count }}</h3>
4    <button @click="increment">+1</button>
5    <button @click="decrement">-1</button>
6    <button @click="reset">Reset</button>
7  </div>
8</template>
9
10<script setup>
11import { useCounter } from './composables/useCounter'
12
13// Destrukturyzacja zwróconych wartości
14const { count, increment, decrement, reset } = useCounter(10)
15</script>

Każdy komponent, który użyje

useCounter()
, dostaje własną, niezależną instancję stanu. Zmiana
count
w jednym komponencie nie wpływa na inny.

Organizacja Plików

Composables przechowujemy w dedykowanym folderze:

1src/
2├── composables/
3│   ├── useTimer.js
4│   ├── useCounter.js
5│   ├── useFetch.js
6│   └── useMarsData.js
7├── components/
8│   ├── StatusBar.vue
9│   └── ModuleCard.vue
10└── App.vue

Praktyczny Composable: useTimer

Timer misji - typowy composable używany w wielu komponentach stacji:

1// composables/useTimer.js
2import { ref, onMounted, onUnmounted } from 'vue'
3
4export function useTimer() {
5  const seconds = ref(0)
6  let intervalId = null
7
8  const formatted = computed(() => {
9    const h = String(Math.floor(seconds.value / 3600)).padStart(2, '0')
10    const m = String(Math.floor((seconds.value % 3600) / 60)).padStart(2, '0')
11    const s = String(seconds.value % 60).padStart(2, '0')
12    return `${h}:${m}:${s}`
13  })
14
15  function start() {
16    intervalId = setInterval(() => {
17      seconds.value++
18    }, 1000)
19  }
20
21  function stop() {
22    if (intervalId) {
23      clearInterval(intervalId)
24      intervalId = null
25    }
26  }
27
28  // Automatyczne zarządzanie cyklem życia
29  onMounted(() => start())
30  onUnmounted(() => stop())
31
32  return { seconds, formatted, start, stop }
33}
1<!-- Użycie w dowolnym komponencie -->
2<template>
3  <p>Czas misji: {{ formatted }}</p>
4</template>
5
6<script setup>
7import { useTimer } from './composables/useTimer'
8
9const { formatted } = useTimer()
10</script>

Composable: useFetch

Pobieranie danych to kolejny typowy wzorzec:

1// composables/useFetch.js
2import { ref } from 'vue'
3
4export function useFetch(url) {
5  const data = ref(null)
6  const error = ref(null)
7  const loading = ref(false)
8
9  async function fetchData() {
10    loading.value = true
11    error.value = null
12
13    try {
14      const response = await fetch(url)
15      data.value = await response.json()
16    } catch (err) {
17      error.value = err.message
18    } finally {
19      loading.value = false
20    }
21  }
22
23  return { data, error, loading, fetchData }
24}
1<template>
2  <div>
3    <p v-if="loading">Ładowanie danych stacji...</p>
4    <p v-else-if="error">Błąd: {{ error }}</p>
5    <ul v-else-if="data">
6      <li v-for="module in data" :key="module.id">{{ module.name }}</li>
7    </ul>
8  </div>
9</template>
10
11<script setup>
12import { onMounted } from 'vue'
13import { useFetch } from './composables/useFetch'
14
15const { data, error, loading, fetchData } = useFetch('/api/modules')
16
17onMounted(() => fetchData())
18</script>

Dlaczego Composables?

| Bez composables | Z composables | |----------------|---------------| | Kopiujesz ten sam kod timera do 5 komponentów | Jeden

useTimer()
, importowany wszędzie | | Zmiana logiki = edycja 5 plików | Zmiana w jednym pliku | | Trudne testowanie (logika w komponentach) | Łatwe testowanie (czyste funkcje) | | Komponenty pełne powtarzającego się kodu | Komponenty skupione na wyświetlaniu |

Ir a CodeWorlds