Kurs Vue.js · Moduł 7: Props i zdarzenia
Zaawansowane Wzorce Komunikacji
W tej lekcji4
Teraz gdy znasz wszystkie narzędzia komunikacji w Vue, zobaczmy jak je łączyć w zaawansowane wzorce - jak kompletny system łączności NOVA LAB.
Problem nie polega już na tym, jak wysłać sygnał, tylko którym kanałem. Ta sama informacja może popłynąć przez props, zdarzenie, v-model, provide albo globalny magazyn stanu. Zły wybór widać dopiero po czasie: dane przepychane przez pięć pięter, dziecko, które po cichu zmienia cudzy stan, albo rodzic grzebiący we wnętrzu dziecka. Poniżej masz mapę kanałów i wzorce, które porządkują ruch na stacji.
Kiedy użyć którego wzorca?
Tabela porządkuje kanały od najbliższego zasięgu do najszerszego. Pinia z ostatniego wiersza to oficjalna biblioteka Vue do zarządzania stanem globalnym - poznasz ją w Bazie Danych Misji.
| Wzorzec | Kiedy użyć |
|---|---|
| Props | Przekazanie danych rodzic → dziecko (1 poziom) |
| Events | Komunikacja dziecko → rodzic (1 poziom) |
| v-model | Two-way binding (formularze, inputy) |
| Provide/Inject | Głęboka komunikacja (2+ poziomów) |
| Pinia | Globalny stan aplikacji |
Props i zdarzenia łączą tylko sąsiadów, v-model to ich para spięta w jeden zapis, provide dociera do dowolnego potomka w poddrzewie, a Pinia jest osiągalna z każdego miejsca aplikacji.
Wzorzec: Readonly Provide + Actions
Bezpieczny wzorzec - potomkowie nie mogą modyfikować stanu bezpośrednio. Dostawca udostępnia stan przez readonly, a obok niego funkcję, która jako jedyna może ten stan zmieniać:
1<!-- Provider -->
2<script setup>
3import { provide, ref, readonly } from 'vue'
4
5const state = ref({ theme: 'dark', lang: 'pl' })
6
7provide('state', readonly(state))
8provide('updateState', (key, value) => {
9 state.value[key] = value
10})
11</script>
12
13<!-- Consumer -->
14<script setup>
15import { inject } from 'vue'
16
17const state = inject('state') // readonly
18const updateState = inject('updateState') // action
19
20updateState('theme', 'light') // OK - przez action
21// state.value.theme = 'light' // readonly: ostrzeżenie w konsoli, zmiana zignorowana
22</script>Przypisanie z ostatniej linii nie rzuci wyjątku: Vue wypisze w konsoli ostrzeżenie, że cel jest tylko do odczytu, i zignoruje zmianę. Każda modyfikacja przechodzi więc przez updateState, czyli przez jedno miejsce, w którym łatwo dodać logowanie albo sprawdzanie danych. Stan wciąż ma jednego właściciela - dostawcę.
Wzorzec: defineExpose
Czasem rodzic musi wydać dziecku polecenie, na przykład "wyzeruj formularz". Komponenty z <script setup> są domyślnie zamknięte: rodzic, który sięgnie do nich przez ref szablonu, nie zobaczy żadnej zmiennej ani funkcji. Makro defineExpose otwiera wybrane z nich - to udostępnianie metod komponentu rodzicowi przez ref:
1<!-- Child.vue -->
2<script setup>
3import { ref } from 'vue'
4
5const count = ref(0)
6const reset = () => { count.value = 0 }
7const validate = () => count.value > 0
8
9defineExpose({ reset, validate })
10</script>
11
12<!-- Parent.vue -->
13<script setup>
14import { ref } from 'vue'
15
16const childRef = ref(null)
17
18const handleReset = () => {
19 childRef.value.reset()
20 const isValid = childRef.value.validate()
21}
22</script>
23
24<template>
25 <Child ref="childRef" />
26 <button @click="handleReset">Reset Child</button>
27</template>Atrybut ref="childRef" wiąże komponent z refem o tej samej nazwie w skrypcie rodzica. Wartość pojawia się dopiero po zamontowaniu - wcześniej childRef.value to null - dlatego metody wołamy w reakcji na kliknięcie. Od Vue 3.5 dokumentacja poleca zamiast ref(null) zapis useTemplateRef('childRef'), który działa tak samo. Rodzic widzi wyłącznie reset i validate, a count pozostaje prywatne.
Kompletny Przepływ Danych
W pełnej aplikacji wszystkie kanały pracują jednocześnie. Tak może wyglądać drzewo komponentów panelu, w którym każde połączenie korzysta z innego wzorca:
1App (provide: globalny stan)
2 └── Layout
3 ├── Header (inject: theme, user)
4 │ └── Avatar (props: user.avatar)
5 │ └── emit('click') → Header
6 └── Content
7 └── Page (props z routera)
8 └── Form (v-model: dane)
9 └── emit('submit') → Page → APIObieg jest zawsze ten sam. Rodzic trzyma stan w ref lub reactive i przekazuje go w props, a dziecko wyświetla dane tylko do odczytu. Dziecko może mieć własny stan roboczy, na przykład szkic wpisywany w formularzu, ale to wciąż jego lokalna sprawa. Gdy chce zmienić dane rodzica, emituje zdarzenie; rodzic aktualizuje stan w handlerze, a Vue ponownie renderuje wszystko, co z tego stanu korzysta. Komponent pośredni, taki jak Layout, może przy tym jednocześnie wstrzykiwać to, co nadaje App, i udostępniać własne dane swojemu poddrzewu. Props dla Page może z kolei dostarczać router - zobaczysz to w Centrum Nawigacji.
Moja rada: wybieraj najbliższy kanał, który wystarcza. defineExpose traktuj jak złącze serwisowe - dla metod w rodzaju reset() czy focus(), a nie jako główną drogę danych. W edytorze poniżej AlertPanel zmienia poziom alarmu wyłącznie przez wstrzyknięte akcje. W projekcie tej lokacji wszystkie wzorce zagrają razem w jednym systemie.
Zapamiętaj: najlepsza łączność na stacji to najkrótsza droga, która dowozi sygnał - i zawsze jeden właściciel danych.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref, provide, readonly } from 'vue'
3import Dashboard from './Dashboard.vue'
4
5// Zaawansowane wzorce komunikacji
6const stationStatus = ref('sprawna')
7const alertLevel = ref(0)
8
9// Provide z readonly + actions pattern
10provide('station', readonly(stationStatus))
11provide('alertLevel', readonly(alertLevel))
12provide('raiseAlert', () => {
13 alertLevel.value = Math.min(alertLevel.value + 1, 5)
14})
15provide('clearAlert', () => {
16 alertLevel.value = 0
17})
18</script>
19
20<template>
21 <div class="nova-lab">
22 <h1>Zaawansowane Wzorce</h1>
23 <p>Stacja: {{ stationStatus }} | Alarm: {{ alertLevel }}/5</p>
24 <Dashboard />
25 </div>
26</template>
27
28<style scoped>
29.nova-lab {
30 background: #0a0e27;
31 color: #00ff88;
32 min-height: 100vh;
33 padding: 20px;
34 font-family: monospace;
35}
36h1 { color: #00ff88; margin-bottom: 15px; }
37</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. Jak wyłączyć automatyczne dziedziczenie atrybutów?
2. Jak uzyskać dostęp do attrs w <script setup>?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj wzorce komunikacji od najbliższej do najdalszej:
- Układanie w poziomie
Ułóż składnię multiple v-model:
- Klikanie w kolejności
Ułóż typowane provide z InjectionKey:
- Układanie w pionie
Uporządkuj przepływ jednokierunkowy danych w Vue:
- Układanie w poziomie
Ułóż składnię defineEmits z walidacją payload:
- Klikanie w kolejności
Ułóż cykl aktualizacji v-model na komponencie:
- Układanie w poziomie
Ułóż kod globalnego provide w main.ts:
- Klikanie w kolejności
Ułóż wzorzec readonly provide:
- Układanie w pionie
Uporządkuj pełny przepływ danych w aplikacji Vue:
- Układanie w pionie
Uporządkuj drzewo komunikacji od góry (app) do dołu (liść):
- Układanie w poziomie
Ułóż składnię defineExpose: