image
https://unsplash.com/photos/etRbDq5J6z8

W Vue mamy 2 typy komponentów: stanowe (czyli te których używamy domyślnie) oraz funkcyjne (bezstanowe). Komponenty możemy definiować na 2 sposoby. W tym artykule przedstawiam sposób użycia komponentów bezstanowych przy użyciu dwóch rodzajów zapisu. Postaram się pokazać zalety oraz wady obu podejść oraz podzielić się własnymi spostrzeżeniami.

Jest to 1 z 2 wpisów na temat rodzaju komponentów w Vue.

Wstęp

Vue przyzwyczaja nas do wygodnego tworzenia komponentów w jednym pliku (SFC - single file component) za pomocą zapisu analogicznego do języka HTML. Jest to zapis intuicyjny i łatwy do nauki. Zdarzają się jednak sytuacje, że ta forma definiowania widoków jest niewygodna lub wręcz utrudnia zrealizowanie pewnych funkcjonalności. Pomocą może być funkcja render (RF), która umożliwia definiowanie elementów naszego komponentu za pomocą czystego języka JavaScript.

Na wstępie wspomnę, że nie ma różnicy w ostatecznym sposobie funkcjonowania komponentu zarówno przy użyciu SFC czy funkcji render. Template'y Vue są kompilowane do funkcji render, więc tworząc ten sam komponent na 2 sposoby otrzymamy dokładnie ten sam wynik.

Drugim interesującym faktem może być informacja co zwraca funkcja render. W wyniku jej wywołania otrzymamy obiekt virtual node (VNode), czyli element budujący VirtualDOM. To właśnie VNode'y będące wynikiem kompilacji komponentów tworzą VirtualDOM, który pozwala na efektywne śledzenie i propagowanie zmian.

Przechodząc do praktycznego przykładu, załóżmy, że naszym celem jest stworzenie komponentu, który w zależności od parametru będzie komponentem<router-link>, elementem <a> lub <button>. Komponent dla router-link i a ma przyjąć props to, a dla button zwrócić event @click.

Przyjrzyjmy się implementacji takiego komponentu. W katalogu components zostały umieszczone 2 pliki: button-link.vue oraz button-link.js.

komponent .vue

W pliku button-link.vue w tradycyjny sposób tworzymy komponent, który przy użyciu dyrektywy v-if wyświetla odpowiedni rodzaj buttona lub linku. Warto w tym miejscu zwrócić uwagę na słowo functional w tagu <template>. Ten zapis jest analogiczny to zapisu functional: true w części <script>, więc można ich używać wymiennie. Nie mamy dostępu do this, a wszystkie parametry komponentu są przekazywane przez obiekt context. Zatem do propsów odwołujemy się po prostu props.my_value. Inaczej też wygląda przypisywanie eventów. Mamy do dyspozycji pole listeners, które przekazuje eventy przypięte do naszego komponentu. Należy je zatem przypisać do buttona taki sposób v-on="listeners" Funkcja isLink jest dostępna przez pole $options i nie ma dostępu do propsów komponentu, więc należy je przekazać przez parametr.

Pozostaje jeszcze jeden problem, który nie jest widoczny w tym przykładzie, a powstanie gdy użyjemy zamiast komponentu <router-link> innego komponentu stanowego lub wcześniej niezarejestrowanego w global scope. Komponent funkcyjny nie "rozumie" pola components i nie pozwoli zarejestrować wewnątrz siebie innego komponentu :sorry . Nie dostaniesz nawet informacji o błędzie.

komponent .js

Przejdźmy do użycia funkcji render. W komponentach funkcyjnych otrzymuje 2 parametry: funkcję callback zwracającą VNode oraz obiekt context.

render: function (createElement, context) {}

Po dokładny opis funkcji createElement odsyłam do dokumentacji https://vuejs.org/v2/guide/render-function.html#createElement-Arguments . W naszym przykładzie w funkcji render sprawdzamy czy jest zdefiniowany parametr to i przekazujemy odpowiedni element/komponent do funkcji createElement. W drugim parametrze przekazujemy obiekt z dodatkowymi klasami, listenerami, natomiast w trzecim zawartość np. string, VNode lub w naszym wypadku context.slots().default.

Oba komponenty różnią się zapisem, ale działają identycznie.

Podsumowanie

Podsumujmy powyższy przykład w dwóch kategoriach - pod kątem użyteczności funkcji render w stosunku do <template> oraz pod kątem użyteczności samych komponentów bezstanowych (funkcyjnych).

Zalety użycia funkcji render:

  • możliwość skorzystania z funkcji czystego javascriptu lub typescriptu
  • zwięzłość zapisu
  • często większe możliwości manipulowania ostatecznym wyglądem
  • można łatwo zdefiniować renderless komponent
  • wsparcie dla JSX

Wady użycia funkcji render:

  • zapis i skomplikowanie - dla kogoś kto widzi taki zapis pierwszy raz, jest to kompletnie niezrozumiałe. Próg wejścia jest stosunkowo wysoki w porównaniu do reszty frameworka. Mimo, że jestem oswojony z taką formą zapisu, często od niej odchodzę z racji ograniczonej przejrzystości.
  • zdecydowanie nie nadaje do użycia gdy mamy kilka poziomów zagłębień. Musimy w takiej sytuacji, zagnieżdżać w sobie funkcje createElement.

Zalety komponentów funkcyjnych:

  • niższy narzut (brak instancji) - nie widziałem benchmarków jak to realnie przekłada się na wydajność
  • brak lifehooków - więc idealnie nadają się do prezentacji

Wady:

  • w stosunku do środowiska reactowego bardzo mało uwagi od twórców, co skutkuje skąpą dokumentacją i brakiem "recipies" lub dobrych praktyk
  • brak możliwości zagnieżdżania komponentów niezarejestrowanych wcześniej,
  • nieintuicyjne przypinanie eventów