Kurs Vue.js · Moduł 15: TypeScript i Nuxt

Template Refs i Provide/Inject z typami

3 min czytania
W tej lekcji4

W zaawansowanych systemach stacji NOVA LAB musimy czasem bezpośrednio odwoływać się do elementow DOM lub przekazywać dane między komponentami bez props drilling. TypeScript pomaga nam robić to bezpiecznie.

Template Refs z typami

Template ref pozwala uzyskać referencję do elementu DOM:

1<script setup lang="ts">
2import { ref, onMounted } from 'vue'
3
4// Typ elementu HTML - ref może być null przed montowaniem
5const inputRef = ref<HTMLInputElement | null>(null)
6const canvasRef = ref<HTMLCanvasElement | null>(null)
7
8onMounted(() => {
9  // Po montowaniu ref nie jest już null
10  inputRef.value?.focus()
11
12  const ctx = canvasRef.value?.getContext('2d')
13  if (ctx) {
14    ctx.fillStyle = '#00ff88'
15    ctx.fillRect(0, 0, 100, 100)
16  }
17})
18</script>
19
20<template>
21  <input ref="inputRef" placeholder="Wpisz komendę..." />
22  <canvas ref="canvasRef" width="400" height="300" />
23</template>

Typ HTMLInputElement | null mówi TypeScriptowi, że ref może być null (zanim komponent zostanie zamontowany).

Referencje do komponentów

Można też uzyskać referencję do komponentu dziecka:

1<script setup lang="ts">
2import { ref } from 'vue'
3import SensorPanel from './SensorPanel.vue'
4
5// InstanceType<typeof Component> daje typ instancji komponentu
6const sensorRef = ref<InstanceType<typeof SensorPanel> | null>(null)
7
8function refreshSensors() {
9  // Dostęp do publicznych metod komponentu
10  sensorRef.value?.refresh()
11}
12</script>
13
14<template>
15  <SensorPanel ref="sensorRef" />
16  <button @click="refreshSensors">Odśwież sensory</button>
17</template>

InjectionKey i Provide/Inject

provide/inject pozwala przekazywać dane w dół drzewa komponentów. InjectionKey<T> zapewnia bezpieczeństwo typów:

1<!-- types.ts -->
2<script lang="ts">
3import type { InjectionKey, Ref } from 'vue'
4
5export interface StationConfig {
6  name: string
7  maxCrew: number
8  sector: string
9}
10
11// Klucz z typem - zapewnia że provide i inject używają tego samego typu
12export const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
13export const ThemeKey: InjectionKey<Ref<'dark' | 'light'>> = Symbol('Theme')
14</script>
1<!-- Provider (rodzic) -->
2<script setup lang="ts">
3import { provide, ref } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6const theme = ref<'dark' | 'light'>('dark')
7
8provide(StationConfigKey, {
9  name: 'NOVA LAB',
10  maxCrew: 100,
11  sector: 'Alpha-7'
12})
13
14provide(ThemeKey, theme)
15</script>
1<!-- Consumer (dziecko/wnuk) -->
2<script setup lang="ts">
3import { inject } from 'vue'
4import { StationConfigKey, ThemeKey } from './types'
5
6// TypeScript zna typ! StationConfig | undefined
7const config = inject(StationConfigKey)
8
9// Z wartością domyślną - nie jest undefined
10const theme = inject(ThemeKey, ref('dark'))
11
12// Używamy optional chaining bo config może być undefined
13console.log(config?.name)    // 'NOVA LAB'
14console.log(theme.value)     // 'dark'
15</script>

Wzorzec z wartością domyślną

Aby uniknąć undefined, można użyć wartości domyślnej:

1const config = inject(StationConfigKey, {
2  name: 'Unknown',
3  maxCrew: 0,
4  sector: 'N/A'
5})
6// Teraz config jest zawsze StationConfig (nigdy undefined)

Można też użyć funkcji fabrykującej:

1const config = inject(StationConfigKey, () => ({
2  name: 'Default',
3  maxCrew: 50,
4  sector: 'Beta'
5}), true)  // trzeci arg = true oznacza że to factory function
Kod do tej lekcji: App.vue
1<script setup lang="ts">
2import { ref, provide, onMounted, type InjectionKey } from 'vue'
3import InfoPanel from './InfoPanel.vue'
4
5// --- Template Ref ---
6const searchInput = ref<HTMLInputElement | null>(null)
7const searchQuery = ref('')
8
9onMounted(() => {
10  // Po zamontowaniu - focus na input
11  searchInput.value?.focus()
12})
13
14function clearSearch() {
15  searchQuery.value = ''
16  searchInput.value?.focus()
17}
18
19// --- InjectionKey ---
20interface StationConfig {
21  name: string
22  sector: string
23  year: number
24}
25
26const StationConfigKey: InjectionKey<StationConfig> = Symbol('StationConfig')
27
28// Provide z typem
29provide(StationConfigKey, {
30  name: 'NOVA LAB',
31  sector: 'Alpha-7',
32  year: 2087
33})
34
35// Eksportujemy klucz aby dziecko moglo go uzyc
36provide('configKey', StationConfigKey)
37</script>
38
39<template>
40  <div class="nova-lab">
41    <h1>Template Refs & Provide/Inject</h1>
42
43    <div class="search-box">
44      <input
45        ref="searchInput"
46        v-model="searchQuery"
47        placeholder="Szukaj w systemach stacji..."
48        class="search-input"
49      />
50      <button @click="clearSearch" class="btn">Wyczysc</button>
51    </div>
52
53    <p v-if="searchQuery" class="query-info">
54      Szukasz: <strong>{{ searchQuery }}</strong>
55    </p>
56
57    <InfoPanel />
58  </div>
59</template>
60
61<style scoped>
62.nova-lab {
63  background: #0a0e27;
64  color: #00ff88;
65  min-height: 100vh;
66  padding: 20px;
67  font-family: monospace;
68}
69h1 { color: #00ff88; margin-bottom: 15px; }
70.search-box { display: flex; gap: 8px; margin-bottom: 10px; }
71.search-input {
72  flex: 1;
73  background: rgba(0,180,216,0.1);
74  border: 2px solid #00b4d8;
75  color: #00ff88;
76  padding: 10px 14px;
77  border-radius: 8px;
78  font-family: monospace;
79  font-size: 14px;
80  outline: none;
81}
82.search-input:focus { border-color: #00ff88; }
83.btn {
84  background: #00b4d8;
85  color: white;
86  border: none;
87  padding: 10px 16px;
88  border-radius: 8px;
89  cursor: pointer;
90}
91.query-info { color: #00b4d8; font-size: 13px; }
92</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Dlaczego template ref definiujemy jako ref<HTMLInputElement | null>(null)?

  2. 2. Co zapewnia InjectionKey<T> w systemie provide/inject?

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj kroki pracy z typowanym template ref:

  • Edytor kodu

    Zdefiniuj InjectionKey, użyj provide w rodzicu i inject w dziecku.

  • Klikanie w kolejności

    Ułóż kroki tworzenia typowanego provide/inject:

  • Układanie w poziomie

    Ułóż składnię typowanej referencji do komponentu:

Przydatne artykuły