Kurs Vue.js · Moduł 7: Props i zdarzenia

Zaawansowane Wzorce Komunikacji

4 min czytania
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.

WzorzecKiedy użyć
PropsPrzekazanie danych rodzic → dziecko (1 poziom)
EventsKomunikacja dziecko → rodzic (1 poziom)
v-modelTwo-way binding (formularze, inputy)
Provide/InjectGłęboka komunikacja (2+ poziomów)
PiniaGlobalny 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 → API

Obieg 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. 1. Jak wyłączyć automatyczne dziedziczenie atrybutów?

  2. 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:

Przydatne artykuły