Reaktywność w Vue

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Reaktywność to zdolność zmiennej (tablicy, łańcucha, liczby, obiektu itp.) do aktualizacji, gdy jej wartość lub jakakolwiek inna zmienna, do której się odwołuje, zostanie zmieniona po deklaracji.

W tym artykule przyjrzymy się reaktywności w Vue, jak to działa i jak możemy tworzyć reaktywne zmienne za pomocą nowo utworzonych metod i funkcji. Ten artykuł jest skierowany do programistów, którzy dobrze rozumieją, jak działa Vue 2.x i chcą zapoznać się z nowym Vue 3.

Zbudujemy prostą aplikację, aby lepiej zrozumieć ten temat. Kod tej aplikacji można znaleźć na GitHub.

Domyślnie JavaScript nie jest reaktywny . Oznacza to, że jeśli utworzymy zmienną boy i odniesiemy się do niej w części A naszej aplikacji, a następnie przystąpimy do modyfikacji boy w części B, część A nie zostanie zaktualizowana o nową wartość boy .

 let framework = 'Vue'; let sentence = `${framework} is awesome`; console.log(sentence) // logs "Vue is awesome" framework = 'React'; console.log(sentence) //should log "React is awesome" if 'sentence' is reactive.

Powyższy fragment kodu jest doskonałym przykładem niereaktywnej natury JavaScript — stąd, dlaczego zmiana nie jest odzwierciedlona w zmiennej sentence .

W Vue 2.x props , computed i data() były domyślnie reaktywne, z wyjątkiem właściwości, które nie są obecne w data podczas tworzenia takich komponentów. Oznacza to, że gdy komponent zostanie wstrzyknięty do DOM, tylko istniejące właściwości w obiekcie data komponentu spowodują aktualizację komponentu, jeśli i kiedy zmienią się te właściwości.

Wewnętrznie Vue 3 używa obiektu Proxy (funkcja ECMAScript 6) w celu zapewnienia, że ​​te właściwości są reaktywne, ale nadal zapewnia opcję użycia Object.defineProperty z Vue 2 do obsługi Internet Explorera (ECMAScript 5). Ta metoda definiuje nową właściwość bezpośrednio na obiekcie lub modyfikuje istniejącą właściwość obiektu i zwraca obiekt.

Na pierwszy rzut oka i ponieważ większość z nas już wie, że reaktywność nie jest niczym nowym w Vue, korzystanie z tych właściwości może wydawać się niepotrzebne, ale Options API ma swoje ograniczenia, gdy mamy do czynienia z dużą aplikacją z funkcjami wielokrotnego użytku w kilku części aplikacji. W tym celu wprowadzono nowy interfejs Composition API, który pomaga w abstrahowaniu logiki w celu ułatwienia czytania i utrzymania bazy kodu. Ponadto możemy teraz z łatwością sprawić, że każda zmienna będzie reaktywna, niezależnie od jej typu danych, korzystając z dowolnych nowych właściwości i metod.

Gdy używamy opcji setup , która służy jako punkt wejścia dla interfejsu Composition API, obiekt data , computed właściwości i methods są niedostępne, ponieważ instancja komponentu nie została jeszcze utworzona podczas wykonywania setup . Uniemożliwia to skorzystanie z wbudowanej reaktywności w którejkolwiek z tych funkcji podczas setup . W tym samouczku poznamy wszystkie sposoby, w jakie możemy to zrobić.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Metoda reaktywna

Zgodnie z dokumentacją metoda reactive , która jest odpowiednikiem Vue.observable() w Vue 2.6, może być przydatna, gdy próbujemy utworzyć obiekt, którego wszystkie właściwości są reaktywne (tak jak obiekt data w opcjach API). Pod maską obiekt data w interfejsie API Options używa tej metody, aby wszystkie zawarte w nim właściwości były reaktywne.

Ale możemy stworzyć własny reaktywny obiekt w ten sposób:

 import { reactive } from 'vue' // reactive state let user = reactive({ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "[email protected]", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" }, "cars": { "number": 0 } })

Tutaj zaimportowaliśmy metodę reactive z Vue, a następnie zadeklarowaliśmy naszą zmienną user , przekazując jej wartość do tej funkcji jako argument. W ten sposób sprawiliśmy, że user jest reaktywny, a zatem, jeśli użyjemy user w naszym szablonie i jeśli obiekt lub właściwość tego obiektu powinna się zmienić, ta wartość zostanie automatycznie zaktualizowana w tym szablonie.

ref

Tak jak mamy metodę, aby obiekty były reaktywne, potrzebujemy również innych samodzielnych wartości pierwotnych (łańcuchy, wartości logiczne, wartości niezdefiniowane, liczby itp.) i tablic. Podczas opracowywania pracowalibyśmy z tymi innymi typami danych, jednocześnie potrzebując ich reaktywności. Pierwszym podejściem, o którym moglibyśmy pomyśleć, byłoby użycie reactive i przekazanie wartości zmiennej, którą chcemy uczynić reaktywną.

 import { reactive } from 'vue' const state = reactive({ users: [], });

Ponieważ reactive ma głęboką konwersję reaktywną, user jako usługa również będzie reaktywny, osiągając w ten sposób nasz cel; w związku z tym user zawsze aktualizowałby w dowolnym miejscu w szablonie takiej aplikacji. Ale dzięki właściwości ref możemy sprawić, że każda zmienna z dowolnym typem danych stanie się reaktywna, przekazując wartość tej zmiennej do ref . Ta metoda działa również w przypadku obiektów, ale zagnieżdża obiekt o jeden poziom głębiej niż w przypadku metody reactive .

 let property = { rooms: '4 rooms', garage: true, swimmingPool: false } let reactiveProperty = ref(property) console.log(reactiveProperty) // prints { // value: {rooms: "4 rooms", garage: true, swimmingPool: false} // }

Pod maską ref pobiera przekazany argument i zamienia go na obiekt z kluczem value . Oznacza to, że możemy uzyskać dostęp do naszej zmiennej przez wywołanie variable.value , a także możemy zmodyfikować jej wartość, wywołując ją w ten sam sposób.

 import {ref} from 'vue' let age = ref(1) console.log(age.value) //prints 1 age.value++ console.log(age.value) //prints 2

Dzięki temu możemy zaimportować ref do naszego komponentu i stworzyć zmienną reaktywną:

 <template> <div class="home"> <form @click.prevent=""> <table> <tr> <th>Name</th> <th>Username</th> <th>email</th> <th>Edit Cars</th> <th>Cars</th> </tr> <tr v-for="user in users" :key="user.id"> <td>{{ user.name }}</td> <td>{{ user.username }}</td> <td>{{ user.email }}</td> <td> <input type="number" name="cars" v-model.number="user.cars.number" /> </td> <td> <cars-number :cars="user.cars" /> </td> </tr> </table> <p>Total number of cars: {{ getTotalCars }}</p> </form> </div> </template> <script> // @ is an alias to /src import carsNumber from "@/components/cars-number.vue"; import axios from "axios"; import { ref } from "vue"; export default { name: "Home", data() { return {}; }, setup() { let users = ref([]); const getUsers = async () => { let { data } = await axios({ url: "data.json", }); users.value = data; }; return { users, getUsers, }; }, components: { carsNumber, }, created() { this.getUsers(); }, computed: { getTotalCars() { let users = this.users; let totalCars = users.reduce(function(sum, elem) { return sum + elem.cars.number; }, 0); return totalCars; }, }; </script>

Tutaj zaimportowaliśmy ref , aby utworzyć zmienną reaktywną users w naszym komponencie. Następnie zaimportowaliśmy axios , aby pobrać dane z pliku JSON w folderze public , i zaimportowaliśmy nasz komponent carsNumber , który będziemy tworzyć później. Następną rzeczą, którą zrobiliśmy, było utworzenie zmiennej reaktywnej users przy użyciu metody ref , aby users mogli aktualizować, gdy tylko zmieni się odpowiedź z naszego pliku JSON.

Stworzyliśmy również funkcję getUser , która pobiera tablicę users z naszego pliku JSON za pomocą axios i przypisaliśmy wartość z tego żądania do zmiennej users . Na koniec utworzyliśmy obliczoną właściwość, która oblicza całkowitą liczbę samochodów, które mają nasi użytkownicy, ponieważ zmodyfikowaliśmy ją w sekcji szablonu.

Należy zauważyć, że podczas uzyskiwania dostępu do właściwości ref , które są zwracane w sekcji szablonu lub poza setup() , są one automatycznie odpakowywane. Oznacza to, że .value refs aby uzyskać do nich dostęp. Ponieważ users to tablica, moglibyśmy po prostu użyć users , a nie users.value w getTotalCars .

W sekcji szablonu pokazaliśmy tabelę wyświetlającą informacje o każdym użytkowniku wraz z komponentem <cars-number /> . Ten komponent akceptuje rekwizyt cars , który jest wyświetlany w wierszu każdego użytkownika jako liczba posiadanych samochodów. Ta wartość jest aktualizowana za każdym razem, gdy wartość cars zmienia się w obiekcie użytkownika , czyli dokładnie tak, jak działałby obiekt data lub computed właściwość, gdybyśmy pracowali z interfejsem API Options.

toRefs

Gdy używamy interfejsu Composition API, funkcja setup akceptuje dwa argumenty: props i context . Te props są przekazywane z komponentu do setup() i umożliwiają dostęp do właściwości komponentu z wnętrza nowego interfejsu API. Metoda ta jest szczególnie przydatna, ponieważ pozwala na destrukturyzację obiektów bez utraty reaktywności.

 <template> <p>{{ cars.number }}</p> </template> <script> export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { console.log(props); // prints {gender: "female", cars: Proxy} }, }; </script> <style></style>

Aby użyć wartości, która jest obiektem z props w Composition API, zapewniając jednocześnie zachowanie reaktywności, używamy toRefs . Ta metoda pobiera obiekt reaktywny i przekształca go w zwykły obiekt, w którym każda właściwość oryginalnego obiektu reaktywnego staje się ref . Oznacza to, że cars podpierają…

 cars: { number: 0 }

… stałoby się teraz tym:

 { value: cars: { number: 0 }

Dzięki temu możemy korzystać z cars w dowolnej części interfejsu API konfiguracji, zachowując jednocześnie jego reaktywność.

 setup(props) { let { cars } = toRefs(props); console.log(cars.value); // prints {number: 0} },

Możemy obserwować tę nową zmienną za pomocą watch interfejsu Composition API i reagować na tę zmianę w dowolny sposób.

 setup(props) { let { cars } = toRefs(props); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }

toRef

Innym częstym przypadkiem użycia, z jakim możemy się spotkać, jest przekazanie wartości , która niekoniecznie jest obiektem, ale raczej jednym z typów danych, które działają z ref (tablica, liczba, łańcuch, boolean itp.). Dzięki toRef możemy stworzyć reaktywną właściwość (tj. ref ) ze źródłowego obiektu reaktywnego. Spowoduje to, że właściwość pozostanie reaktywna i będzie aktualizowana po każdej zmianie źródła nadrzędnego.

 const cars = reactive({ Toyota: 1, Honda: 0 }) const NumberOfHondas = toRef(state, 'Honda') NumberOfHondas.value++ console.log(state.Honda) // 1 state.Honda++ console.log(NumberOfHondas.value) // 2

Tutaj stworzyliśmy reaktywny obiekt metodą reactive , z właściwościami Toyota i Honda . Wykorzystaliśmy również toRef , aby utworzyć zmienną reaktywną z Honda . Z powyższego przykładu widać, że gdy aktualizujemy Honda za pomocą obiektu cars reaktywnych lub NumberOfHondas , wartość jest aktualizowana w obu przypadkach.

Ta metoda jest podobna, a jednak tak różna od metody toRefs , którą omówiliśmy powyżej, w tym sensie, że utrzymuje połączenie ze swoim źródłem i może być używana do łańcuchów, tablic i liczb. W przeciwieństwie do toRefs , nie musimy martwić się o istnienie właściwości w jej źródle w momencie tworzenia, ponieważ jeśli ta właściwość nie istnieje w momencie tworzenia tego ref i zamiast tego zwraca null , nadal będzie przechowywana jako prawidłowa właściwość, z wprowadzoną formą watcher , tak że gdy ta wartość się zmieni, ten ref utworzony za pomocą toRef również zostanie zaktualizowany.

Możemy również użyć tej metody do stworzenia reaktywnej właściwości z props . Wyglądałoby to tak:

 <template> <p>{{ cars.number }}</p> </template> <script> import { watch, toRefs, toRef } from "vue"; export default { props: { cars: { type: Object, required: true, }, gender: { type: String, required: true, }, }, setup(props) { let { cars } = toRefs(props); let gender = toRef(props, "gender"); console.log(gender.value); watch( () => cars, (cars, prevCars) => { console.log("deep ", cars.value, prevCars.value); }, { deep: true } ); }, }; </script>

Tutaj stworzyliśmy ref , który byłby oparty na właściwości gender uzyskanej z props . Przydaje się to, gdy chcemy wykonać dodatkowe operacje na rekwizycie konkretnego komponentu.

Wniosek

W tym artykule przyjrzeliśmy się, jak działa reaktywność w Vue, korzystając z niektórych nowo wprowadzonych metod i funkcji z Vue 3. Zaczęliśmy od przyjrzenia się, czym jest reaktywność i jak Vue używa zakulisowego obiektu Proxy , aby to osiągnąć. Przyjrzeliśmy się również, jak możemy tworzyć obiekty reaktywne za pomocą reactive i jak tworzyć reaktywne właściwości za pomocą ref .

Na koniec przyjrzeliśmy się, jak przekształcić reaktywne obiekty w zwykłe obiekty, z których każda właściwości jest ref wskazującą na odpowiednią właściwość oryginalnego obiektu, i widzieliśmy, jak utworzyć ref dla właściwości w reaktywnym obiekcie źródłowym.

Dalsze zasoby

  • „Proxy” (obiekt), MDN Web Docs
  • „Podstawy reaktywności”, Vue.js
  • „Odniesienia”, Vue.js
  • setup modułu Lifecycle Hook Registration Inside”, Vue.js