Zrozumieć Vue components

Vue to framework Javascript do tworzenia aplikacji internetowych. Podobnie jak w przypadku innych frameworków, umożliwia korzystanie z komponentów wielokrotnego użytku. Sprawdź jak używać komponentów w Vue

Wcześniej pokazałem, jak rozpocząć pracę z Vuei obiecałem, że będzie druga część, i proszę bardzo!

Spoiler alert: będzie nawet trzecia seria, ale do rzeczy!

Skoro znamy już podstawy Vue, musimy teraz zagłębić się w platformę Vue, aby stworzyć niestandardowy komponent. Najpierw tworzymy komponent reprezentujący każdego gracza w naszej drużynie piłkarskiej. W trakcie całego procesu uczymy się kilku podstawowych pojęć, takich jak wywoływanie komponentów w innym komponencie, wysyłanie danych do komponentu za pośrednictwem rekwizytów lub stosowanie podejścia provide/inject.

Podstawy komponentów

Komponenty są instancjami Vue wielokrotnego użytku. W aplikacji, nad którą zacząłem pracować w ostatnim artykule, znajduje się główny komponent: App.vue.

Wszelkie inne komponenty akceptują te same właściwości, co komponent główny: dane, obliczone, metody, obserwacje itp.

komponenty vue są instancjami vue wielokrotnego użytku

Trzy części komponentu w Vue

Pojedynczy plik komponentu vue składa się z trzech części: Składnia HTML określająca widok wizualny dla komponentu. JavaScript zapewnia listę właściwości do tworzenia komponentu, przy użyciu standardowej składni eksportu modułów JavaScript w JS. Arkusze stylów są używane do definiowania najlepszych interfejsów użytkownika.

Należy utworzyć nowy plik (zazwyczaj w folderze komponentów). Nazwijmy ten plik: PlayerCard.vue.

Najpierw dodaj sekcję szablonu z elementami HTML:

<template>
    <h1>This is a new component</h1>
</template>

Eksport komponentów Vue

Szczerze mówiąc, przez długi czas myślałem, że muszę dodać kolejną sekcję pod sekcją Szablony: i dodać obiekt eksportu, taki jak ten:

<script>
export default {}
</script>

Nie jest to jednak konieczne. Komponenty może mieć tylko w sekcję template. W każdym razie, zazwyczaj w aplikacjach znajdują się bardziej zaawansowane komponenty, więc dobrze jest dodać domyślny obiekt eksportu z właściwością name.

<template>
    <h1>This is a new component</h1>
</template>
<script>
export default {
    name: 'PlayerdCard'
}
</script>

Jak importować komponenty Vue

Ponieważ wyeksportowaliśmy komponenty, możemy użyć ich w innym miejscu. W tym przypadku zaimportujemy komponent PlayerdCard Vue do komponentu App. Aby to zrobić, zróbmy to:

Importowanie komponentów podrzędnych do komponentów nadrzędnych wewnątrz znaczników skryptu

<script>
import PlayerCard from './components/PlayerCard.vue';

export default {
  name: 'App',
(...)

Rejestracja komponentu podrzędnego w komponencie nadrzędnym

Musimy poinformować komponent App, że może korzystać z komponentu PlayerdCard. Istnieje właściwość components , która jest odpowiedzialna za rejestrowanie komponentów:

<script>
import PlayerCard from './components/PlayerCard.vue';


export default {
  name: 'App',
  components: { PlayerCard },
(...)

Na koniec: Użyj komponentu podrzędnego do renderowania szablonu komponentu podrzędnego.

Aby wyrenderować komponent PlayerCard, należy umieścić niestandardowy element wewnątrz szablonu komponentu App. Zaimportowaliśmy komponent jako PlayerCard, więc jest to nazwa elementu niestandardowego:

<template>
  <PlayerCard/>
</template>

<script>
import PlayerCard from './components/PlayerCard.vue';

export default {
  name: 'App',
  components: { PlayerCard },
  // other stuff below
  data() {
    return {
      players: [
      ]
    }
  },
  computed: {
  },
  methods: {
  watch: {
  }
}
</script>
<style>
</style>

Komponenty globalne i lokalne

Zarejestrowaliśmy globalnie nasz pierwszy komponent w App.vue, komponent główny. Jest to typowe podejście, ale czasami określone komponenty są używane w mniejszej części aplikacji.

Vue pozwala na rejestrowanie komponentów w innych komponentach. Możesz więc zarejestrować komponent w instancji komponentu PlayerCard, a następnie użyć go w dowolnych jego elementach potomnych.

Oznacza to, że komponent jest zarejestrowany lokalnie i nie będzie dostępny w komponentach nadrzędnych – w naszym przypadku w obiekcie komponentu aplikacji.

Komunikacja między komponentami

Komponent PlayerdCard działa, ale nie jest zbyt pomocny. Dodajmy do niego trochę treści:

<template>
    <div class="player">
        <h1>{{ player.name }}</h1>
        <img :src="player.image" :alt="player.name" />
        <p><strong>{{ player.club }}, {{ player.country }}</strong></p>
        <p>Position: {{ player.position }}</p>
        <p>
            Price: {{ player.price }}
            <strong v-if="player.playerLabel">{{ player.playerLabel }}</strong>
        </p>
        <button type="button" @click="toggleForSale()">
            <span v-if="player.forSale">Remove from transfer list</span>
            <span v-if="!player.forSale">Add to transfer list</span>
        </button>
    </div>
</template>

Obiekt gracza jest używany w wielu miejscach, ale obiekt komponentu nie zawiera danych gracza. Jak przekazać dane gracza do komponentu?

Wprowadzenie rekwizytów

Aby przekazać dane do komponentów, można użyć props. Props to atrybuty HTML, które można, powiedzmy, przenieść do zakresu komponentu.

Po pierwsze, musimy zdefiniować, że komponent PlayerCard może odbierać właściwości gracza:

// PlayerCard.vue
export default {
    name: 'PlayerdCard',
    props: ['player']
}

Następnie możemy przekazać właściwość gracza do PlayerCard w App.vue

<template>
  <PlayerCard :player="players[0]"/>
</template>

Player to tablica zdefiniowana w danych komponentu App:

export default {
  name: 'App',
  components: { PlayerCard },
  data() {
    return {
      players: [
        {
          id: 1,
          name: 'Mo Salah',
          club: 'Liverpool FC',
          country: 'Egypt',
          position: 'Striker',
          price: 2000,
          image: './avatar.png',
          forSale: false 
        },
        {
          id: 2,
          name: 'Robert Lewandowski',
          club: 'FC Bayern',
          country: 'Poland',
          position: 'Striker',
          price: 3000,
          image: './avatar.png',
          forSale: true 
        }
      ]
    }
  }
}
</script>

Aby przykład był bardziej dokładny, wykonajmy iterację przez tablicę graczy za pomocą dyrektywy v for i wyświetlmy wszystkich graczy na ekranie:

<template>
  <PlayerCard v-for="player in players" :key="player.id" :player="player"/>
</template>

Obsługiwane props

Powyżej przekazaliśmy obiekt jako właściwość, ale można również podać inne typy. Przyjrzyj się typom props obsługiwanym przez Vue:

  • String
  • Liczba
  • Wartość logiczna
  • Tablica
  • Obiekt
  • Data
  • Funkcja
  • Symbol

Sprawdzanie poprawności props

Kiedy zarejestrowałem właściwość w komponencie PlayerCard, po prostu wkleiłem nazwę do tablicy właściwości:

props: ['player']

ale istnieje opcja walidacji props. Zobacz:

props: {
    player: Object
}

Teraz komponent oczekuje, że gracz będzie obiektem, więc każdy inny typ przekazany do komponentu spowoduje wyświetlenie ostrzeżenia w konsoli.

Możesz również określić, że rekwizyt jest wymagany:

props: {
    player: Object,
    required: true
}

Wartość domyślna:

props: {
    player: Object,
    default: {
        name: 'Bot'
   }
}

Poza tym jest więcej opcji. Postępuj zgodnie z dokumentacją Vue, aby poznać je wszystkie. Ostatnią rzeczą, którą chcę tutaj zrobić, jest powtarzanie dokumentów Vue.

Po tym kroku aplikacja renderuje dwóch graczy:

Komponent vue PlayerCard w akcji

Wydarzenia niestandardowe: komunikacja dziecko-rodzic

Nasz pierwszy komponent Vue działa, ale jest kilka błędów. Kiedy klikam „dodaj do listy transferowej”, widzę błąd w konsoli:

Uncaught TypeError: _ctx.toggleForSale is not a function

Dzieje się tak, ponieważ w PlayerCard nie mamy funkcji toggleForSale. Funkcja ta znajduje się w zakresie komponentu App Vue…:

methods: {
  toggleForSale() {
    this.forSale = !this.forSale;
  }
},

Tak czy inaczej, ta funkcja jest przestarzała. Zadziałało to w przypadku, gdy mieliśmy jednego gracza. Teraz mamy tablicę graczy, więc musimy zaktualizować funkcję toggleForSale, aby to umożliwić.

methods: {
  toggleForSale(playerId) {
    const player = this.players.find(player => player.id === playerId);
    player.forSale = !player.forSale;
  }
},

Teraz funkcja otrzymuje parametr playerId, aby zidentyfikować, który gracz powinien zostać zmodyfikowany, ale nadal – funkcja toggleForSale jest w zakresie komponentu App. Czy możliwe jest wywołanie tej metody z innych komponentów Vue? Zwłaszcza z komponentów dla dzieci?

Jest na to sposób: niestandardowe wydarzenia!

komponent vue app js może komunikować się z innymi instancjami vue

Definiowanie zdarzeń niestandardowych

Aby zdefiniować zdarzenie, należy dodać je do tablicy emits w następujący sposób:

export default {
    name: 'PlayerdCard',
    props: ['player'],
    emits: ['toggle-for-sale']
}

Technicznie nie jest to obowiązkowe, ale jest to dobra praktyka, aby definiować zdarzenia, ponieważ wtedy łatwiej jest zrozumieć, jak działają komponenty.

Następnym krokiem jest wyemitowanie naszego niestandardowego zdarzenia, gdy użytkownik kliknie przycisk:

<button type="button" @click="$emit('toggle-for-sale', player.id)">
    <span v-if="player.forSale">Remove from transfer list</span>
    <span v-if="!player.forSale">Add to transfer list</span>
</button>

Na koniec musimy nasłuchiwać tego zdarzenia w komponencie App:

1// App.vue
<template>
  <PlayerCard 
    v-for="player in players" 
    :key="player.id" 
    :player="player"
    @toggle-for-sale="toggleForSale"
  />
</template>

Dla niestandardowego zdarzenia @toggle-for-sale powiązałem funkcję toggleForSale dostępną w App.js i teraz funkcjonalność działa zgodnie z oczekiwaniami.

Sprawdzanie poprawności zdarzeń niestandardowych

Tak samo jak props, zdarzenia niestandardowe mogą być walidowane. Jest to możliwe dzięki zdefiniowaniu emiterów nie jako tablic, ale jako obiektów. Każda właściwość tego obiektu to niestandardowa nazwa zdarzenia i jest to funkcja sprawdzania poprawności:

emits: {
    'toggle-for-sale': (playerId) => {
        if (!playerId) {
            console.warn('Player ID is missing')

            return false;
        }

        return true
    }
}

Prop drilling

Jeśli trafiłeś tutaj z Reacta, prawdopodobnie znasz powszechny problem zwany: props drilling. Jeśli nie, pozwól mi szybko wyjaśnić na przykładzie.

Dodamy dwa komponenty do komponentu PlayerCard:

  • PlayerData
  • PlayerAttributes

Zanim zaczniemy wdrażać te komponenty, dodajemy kilka dodatkowych danych do naszych graczy:

players: [
  {
    id: 1,
    name: 'Mo Salah',
    club: 'Liverpool FC',
    country: 'Egypt',
    position: 'Striker',
    price: 2000,
    image: './avatar.png',
    forSale: false,
    birthday: '10/10/2000',
    growth: '180cm',
    betterLeg: 'right',
    speed: 94,
    shooting: 90,
    passes: 89,
    dribble: 99,
    defense: 30,
    physical: 85
  },
  {
    id: 2,
    name: 'Robert Lewandowski',
    club: 'FC Bayern',
    country: 'Poland',
    position: 'Striker',
    price: 3000,
    image: './avatar.png',
    forSale: false,
    birthday: '20/05/2000',
    growth: '190cm',
    betterLeg: 'right',
    speed: 90,
    shooting: 99,
    passes: 89,
    dribble: 85,
    defense: 78,
    physical: 89
  }
]

Składnik PlayerData

// PlayerCard/PlayerData.vue

<template>
    <div class="player-data">
        <ul>
            <li>
                <strong>Birthday: </strong>
                <span>{{ player.birthday }}</span>
            </li>
            
            <li>
                <strong>Growth: </strong>
                <span>{{ player.growth }}</span>
            </li>


            <li>
                <strong>Better leg: </strong>
                <span>{{ player.betterLeg }}</span>
            </li>
        </ul>
    </div>
</template>
<script>
export default {
    name: 'PlayerData',
    props: ['player'],
}
</script>

Ten komponent renderuje pewne informacje o zawodniku, takie jak data urodzenia, wzrost i lepsza noga.

Zaimportujmy go i zarejestrujmy w komponencie playerCard Vue:

<script>
import PlayerData from './PlayerCard/PlayerData.vue'

export default {
    name: 'PlayerdCard',
    props: ['player'],
    emits: ['toggle-for-sale'],
    components: {
        PlayerData
    }
}
</script>

Teraz możemy użyć go w sekcji szablonu:

// PlayerCard.vue

<template>
    <div class="player">
        (...)

        <div class="additional-data">
            <PlayerData :player="player"/>
        </div>
    </div>
</template>

Składnik PlayerAttributes

Ten komponent będzie prawie taki sam. Różnica polega na tym, że renderuje inne dane:

<template>
    <div class="player-attributes">
        <ul>
            <li>
                <strong>Speed: </strong>
                <span>{{ player.speed }}</span>
            </li>
            
            <li>
                <strong>Shooting: </strong>
                <span>{{ player.shooting }}</span>
            </li>


            <li>
                <strong>Passes: </strong>
                <span>{{ player.passes }}</span>
            </li>

            <li>
                <strong>Dribble: </strong>
                <span>{{ player.passes }}</span>
            </li>


            <li>
                <strong>Passes: </strong>
                <span>{{ player.dribble }}</span>
            </li>


            <li>
                <strong>Defense: </strong>
                <span>{{ player.defense }}</span>
            </li>


            <li>
                <strong>Physical: </strong>
                <span>{{ player.physical }}</span>
            </li>
        </ul>
    </div>
</template>
<script>
export default {
    name: 'PlayerData',
    props: ['player'],
}
</script>

Uwaga: na razie te komponenty wyglądają tak samo, ale w przyszłości zamierzam dodać tam więcej logiki, więc nie martw się, mam pomysł 😆

Ostatnią rzeczą w tym kroku jest zarejestrowanie, zaimportowanie i użycie nowego komponentu w PlayerCard.

Ostateczny kod komponentu PlayerCard:

<template>
    <div class="player">
        <h1>{{ player.name }}</h1>
        <img :src="player.image" :alt="player.name" />
        <p><strong>{{ player.club }}, {{ player.country }}</strong></p>
        <p>Position: {{ player.position }}</p>
        <p>
            Price: {{ player.price }}
            <strong v-if="player.playerLabel">{{ player.playerLabel }}</strong>
        </p>
        <button type="button" 
            @click="$emit('toggle-for-sale', player.id)">
            <span v-if="player.forSale">Remove from transfer list</span>
            <span v-if="!player.forSale">Add to transfer list</span>
        </button>

        <div class="additional-data">
            <PlayerData :player="player"/>
            <PlayerAttributes :player="player"/>
        </div>
    </div>
</template>
<script>
import PlayerData from './PlayerCard/PlayerData.vue';
import PlayerAttributes from './PlayerCard/PlayerAttributes.vue';

export default {
    name: 'PlayerdCard',
    props: ['player'],
    emits: ['toggle-for-sale'],
    components: {
        PlayerData,
        PlayerAttributes
    }
}
</script>

Jest to renderowany playerCard w przeglądarce:

Renderowany komponent pojedynczego pliku Vue

Chciałem ci pokazać problem z wierceniem rekwizytów, jeśli zapomniałeś, i proszę bardzo. Mamy komponent PlayerdCard, który odbiera gracza jako właściwość, a ten komponent ma dzieci: PlayerData i PlayerAttributes, które również otrzymują gracza jako rekwizyt.

Co więcej, komponenty podrzędne PlayerCard również mogą mieć dzieci, które potrzebują gracza. Problem prop drilling polega na przekazywaniu props od rodzica do dziecka. Może to być problematyczne i frustrujące, jeśli masz duże drzewo komponentów.

W aplikacji React istnieje sposób, aby poradzić sobie z tym w inny sposób – używając Context. W vue jest coś znajomego.

provide/inject jako rozwiązanie

Zamiast przekazywać props do każdego komponentu podrzędnego, można podać dane w komponencie nadrzędnym i wstrzyknąć je do komponentów podrzędnych. Zobacz:

export default {
  name: 'App',
  components: { PlayerCard },
  data() {
    return {
      players: [
        {
          id: 1,
          name: 'Mo Salah',
          club: 'Liverpool FC',
          country: 'Egypt',
          position: 'Striker',
          price: 2000,
          image: './avatar.png',
          forSale: false,
          birthday: '10/10/2000',
          growth: '180',
          betterLeg: 'right',
          speed: 94,
          shooting: 90,
          passes: 89,
          dribble: 99,
          defense: 30,
          physical: 85
        },
        {
          id: 2,
          name: 'Robert Lewandowski',
          club: 'FC Bayern',
          country: 'Poland',
          position: 'Striker',
          price: 3000,
          image: './avatar.png',
          forSale: false,
          birthday: '20/05/2000',
          growth: '190',
          betterLeg: 'right',
          speed: 90,
          shooting: 99,
          passes: 89,
          dribble: 85,
          defense: 78,
          physical: 89
        }
      ]
    }
  },
  provide: {
    players: this.players
  },
}

Następnie w każde dziecko można wstrzyknąć graczy:

export default {
    name: 'PlayerData',
    inject: ['player'],
}

Po wstrzyknięciu gracza do komponentów PlayerData i PlayerAttributes, mogę usunąć z nich atrybut gracza w komponencie PlayerCard:

<div class="additional-data">
    <PlayerData />
    <PlayerAttributes/>
</div>

Aplikacja nadal działa, ale dane są przekazywane w inny sposób.

Komponenty dynamiczne

Ostatnią rzeczą, którą chcę pokazać, są komponenty dynamiczne. Teraz mamy dwa komponenty w komponencie PlayerCard: PlayerData i PlayerAttributes, i są one renderowane na ekranie. Chciałbym mieć coś takiego jak zakładki i możliwość zmiany aktywnej zakładki. Wówczas widoczny będzie tylko jeden komponent odpowiadający wybranej zakładce.

Najpierw utwórz nawigację dla zakładek:

// PlayerCard.js
<nav>
    <button type="button" @click="selectTab('PlayerData')">Player Data</button>
    <button type="button" @click="selectTab('PlayerAttributes')">Player Attributes</button>
</nav>

Powiązałem metodę selectTab ze zdarzeniem kliknięcia, więc stwórzmy tę metodę:

methods: {
    selectTab(tab) {
        this.selectedTab = tab;
    } 
},

Ponadto dodaj nową właściwość danych z polem selectedTab:

data() {
    return {
        selectedTab: 'PlayerData'
    }
},

Na koniec wyrenderujmy nasze komponenty dynamicznie, dodając konkretny komponent vue i wiążąc właściwość selectedTab z właściwością tego komponentu w następujący sposób:

<div class="additional-data">
    <component :is="selectedTab"/>
</div>

Dzięki temu możemy renderować dynamiczne komponenty na podstawie właściwości danych, tak jak w tym przykładzie. Gdy właściwość zostanie zmieniona, Vue dynamicznie przełącza komponent i renderuje go.

Kompletny kod zawierający dynamiczne komponenty:

<template>
    <div class="player">
        <h1>{{ player.name }}</h1>
        <img :src="player.image" :alt="player.name" />
        <p><strong>{{ player.club }}, {{ player.country }}</strong></p>
        <p>Position: {{ player.position }}</p>
        <p>
            Price: {{ player.price }}
            <strong v-if="player.playerLabel">{{ player.playerLabel }}</strong>
        </p>
        <button type="button" 
            @click="$emit('toggle-for-sale', player.id)">
            <span v-if="player.forSale">Remove from transfer list</span>
            <span v-if="!player.forSale">Add to transfer list</span>
        </button>


        <nav>
            <button type="button" @click="selectTab('PlayerData')">Player Data</button>
            <button type="button" @click="selectTab('PlayerAttributes')">Player Attributes</button>
        </nav>


<div class="additional-data">
    <component :is="selectedTab"/>
</div>
    </div>
</template>
<script>
import PlayerData from './PlayerCard/PlayerData.vue';
import PlayerAttributes from './PlayerCard/PlayerAttributes.vue';


export default {
    name: 'PlayerdCard',
    props: ['player'],
    emits: ['toggle-for-sale'],
    provide() {
        return {
            player: this.player
        }
    },
    components: {
        PlayerData,
        PlayerAttributes
    },
    data() {
        return {
            selectedTab: 'PlayerData'
        }
    },
    methods: {
        selectTab(tab) {
            this.selectedTab = tab;
        } 
    }
}
</script>

Na razie aplikacja wygląda następująco:

Dynamiczne komponenty w akcji

Nie jest to piękne:😎 ale nie martw się – następnym razem pokażę Ci jak stylizować aplikacje Vue.

Podsumowanie

Vue to framework Javascript do tworzenia aplikacji internetowych. Podobnie jak w przypadku innych frameworków, umożliwia korzystanie z komponentów wielokrotnego użytku. Dzięki temu tworzenie front-endu i budowanie jednostronicowych aplikacji jest łatwe i wydajne.

Dzisiaj pokazałem podstawy dotyczące komponentów:

  • Jak utworzyć i zarejestrować komponent vue
  • jak komponenty komunikują się z innymi komponentami
  • jak używać props
  • Co to jest prop drilling
  • jak korzystać z mechanizmu dostarczania/wstrzykiwania
  • Jak korzystać z komponentów dynamicznych
  • i tak dalej
Udostępnij post:

Możesz także polubić

Kariera w branży technologicznej: Jak rozwijać swoje umiejętności

Jesteś programistą i chciałbyś się rozwijać? W internecie znajdziesz pełno materiałów o tym, jak to zrobić. Pomimo tego nie uciekaj — mam coś, co Cię zaciekawi. Czy wiesz, że Adam Małysz — legendarny polski skoczek, zanim został mistrzem latania, to był dekarzem? Nie śmiem się porównywać z Panem Adamem, natomiast są dwie rzeczy, które nas łączą.

Ja też byłem dekarzem i też udało mi się przebranżowić. Może nie w tak spektakularny sposób, ale jednak. W tym artykule podzielę się z Tobą moim osobistym doświadczeniem, które zdobyłem na drodze od dekarza przez programistę do tech leada i dam Ci wskazówki, które będziesz mógł zastosować, aby się rozwijać i awansować, a może nawet zmienić diametralnie swoją karierę.

Czytaj więcej
AHA stack przywróćmy prostotę frontendu

AHA! Przywróćmy prostotę Frontendu

Czy zastanawiałeś się, dlaczego w dzisiejszych czasach, gdy mamy dostęp do najnowszych technologii i rozwiązań, projekty IT nadal kończą się fiaskiem? Czy nie uważasz, że w wielu przypadkach zamiast upraszczać to komplikujemy sobie życie i pracę? Czasami mniej znaczy więcej, zwłaszcza w świecie frontendu! Czytaj dalej i dowiedz się czym jest AHA stack i jak robić frontend prościej.

Czytaj więcej