Kurs Vue.js · Moduł 15: TypeScript i Nuxt
Template Refs i Provide/Inject z typami
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 functionKod 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. Dlaczego template ref definiujemy jako
ref<HTMLInputElement | null>(null)?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: