Vue 中的反應性

已發表: 2022-03-10
快速總結↬反應性是變量(數組、字符串、數字、對像等)在其值或它引用的任何其他變量在聲明後發生更改時更新的能力。

在本文中,我們將研究 Vue 中的反應性,它是如何工作的,以及我們如何使用新創建的方法和函數來創建反應性變量。 本文面向對 Vue 2.x 工作原理有深入了解並希望熟悉新的 Vue 3 的開發人員。

我們將構建一個簡單的應用程序來更好地理解這個主題。 這個應用程序的代碼可以在 GitHub 上找到。

默認情況下, JavaScript 不是響應式的。 這意味著如果我們創建變量boy並在應用程序的 A 部分中引用它,然後繼續修改 B 部分中的boy ,則 A 部分不會更新為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.

上面的代碼片段是 JavaScript 非響應式特性的完美示例——因此,為什麼更改沒有反映在sentence變量中。

在 Vue 2.x 中, propscomputeddata()默認情況下都是響應式的,但創建此類組件時data中不存在的屬性除外。 這意味著當一個組件被注入到 DOM 中時,只有組件data對像中的現有屬性會在這些屬性發生變化時導致組件更新。

在內部,Vue 3 使用Proxy對象(ECMAScript 6 的一個特性)來確保這些屬性是反應性的,但它仍然提供了使用 Vue 2 中的Object.defineProperty來支持 Internet Explorer(ECMAScript 5)的選項。 此方法直接在對像上定義新屬性,或修改對像上的現有屬性,並返回該對象。

乍一看,由於我們大多數人已經知道反應性在 Vue 中並不新鮮,因此似乎沒有必要使用這些屬性,但是當您處理具有多個可重用函數的大型應用程序時,Options API 有其局限性應用程序的一部分。 為此,引入了新的 Composition API 以幫助抽象邏輯,以使代碼庫更易於閱讀和維護。 此外,我們現在可以使用任何新屬性和方法輕鬆地使任何變量反應,而不管其數據類型如何。

當我們使用setup選項作為 Composition API 的入口點時, data對象、 computed屬性和methods是不可訪問的,因為在執行setup時組件實例尚未創建。 這使得無法利用setup中任何這些功能中的內置反應性。 在本教程中,我們將了解我們可以做到這一點的所有方法。

跳躍後更多! 繼續往下看↓

反應式方法

根據文檔,在 Vue 2.6 中等效於Vue.observable()reactive方法在我們嘗試創建一個所有屬性都是響應式的對象(例如 Options 中的data對象)時很有用API)。 在底層,Options API 中的data對象使用此方法來使其中的所有屬性都具有反應性。

但是我們可以像這樣創建自己的響應式對象:

 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 } })

在這裡,我們從 Vue 導入了reactive方法,然後我們通過將其值作為參數傳遞給該函數來聲明我們的user變量。 在這樣做的過程中,我們使user反應,因此,如果我們在模板中使用user並且如果該對象的對像或屬性應該更改,那麼該值將在該模板中自動更新。

ref

正如我們有一種使對象具有反應性的方法一樣,我們也需要一種方法來使其他獨立的原始值(字符串、布爾值、未定義值、數字等)和數組具有反應性。 在開發過程中,我們將使用這些其他數據類型,同時還需要它們具有反應性。 我們可能會想到的第一種方法是使用reactive並傳入我們想要使其成為響應式的變量的值。

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

因為reactive有很深的響應式轉換,所以user作為屬性也會是響應式的,從而達到我們的目的; 因此, user總是會在此類應用程序的模板中使用它的任何地方進行更新。 但是使用ref屬性,我們可以通過將該變量的值傳遞給ref來使任何具有任何數據類型的變量都具有反應性。 此方法也適用於對象,但它比使用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} // }

在後台, ref接受傳遞給它的這個參數並將其轉換為一個具有value鍵的對象。 這意味著,我們可以通過調用variable.value來訪問我們的變量,也可以通過同樣的方式調用它來修改它的值。

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

有了這個,我們可以將ref導入我們的組件並創建一個反應變量:

 <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>

在這裡,我們導入了ref以便在我們的組件中創建一個響應式users變量。 然後我們導入axios以從public文件夾中的 JSON 文件中獲取數據,並導入我們稍後將創建的carsNumber組件。 我們接下來要做的是使用ref方法創建一個響應式users變量,以便users可以在我們的 JSON 文件的響應發生變化時進行更新。

我們還創建了一個getUser函數,它使用 axios 從我們的 JSON 文件中獲取users數組,並將此請求中的值分配給users變量。 最後,我們創建了一個計算屬性,用於計算用戶擁有的汽車總數,因為我們在模板部分對其進行了修改。

需要注意的是,當訪問在模板部分或setup()之外返回的ref屬性時,它們會自動淺展開。 這意味著作為對象的refs仍然需要一個.value才能被訪問。 因為users是一個數組,我們可以在getTotalCars中簡單地使用users而不是users.value

在模板部分,我們展示了一個顯示每個用戶信息的表格,以及一個<cars-number />組件。 該組件接受一個cars道具,該道具顯示在每個用戶的行中,作為他們擁有的汽車數量。 每當用戶對像中cars的值發生變化時,該值就會更新,這正是我們使用 Options API 時data對像或computed屬性的工作方式。

toRefs

當我們使用 Composition API 時, setup函數接受兩個參數: propscontext 。 這個props從組件傳遞到setup() ,它使得從這個新 API 中訪問組件具有的 props 成為可能。 這種方法特別有用,因為它允許在不失去反應性的情況下解構對象

 <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>

為了在 Composition API 中使用來自props的對象的值,同時確保它保持其反應性,我們使用了toRefs 。 此方法接受一個響應式對象並將其轉換為一個普通對象,其中原始響應式對象的每個屬性都成為一個ref 。 這意味著cars支撐...

 cars: { number: 0 }

......現在會變成這樣:

 { value: cars: { number: 0 }

有了這個,我們可以在設置 API 的任何部分中使用cars ,同時仍然保持其反應性。

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

我們可以使用 Composition API 的watch來觀察這個新變量,並對這個變化做出我們可能想要的反應。

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

toRef

我們可能面臨的另一個常見用例是傳遞一個值,該值不一定是對象,而是與ref一起使用的數據類型之一(數組、數字、字符串、布爾值等)。 使用toRef ,我們可以從源反應對象創建反應屬性(即ref )。 這樣做將確保該屬性保持反應性,並在父源更改時更新。

 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

在這裡,我們使用reactive方法創建了一個響應式對象,具有ToyotaHonda屬性。 我們還利用toRefHonda創建了一個反應變量。 從上面的示例中,我們可以看到,當我們使用響應式cars對像或NumberOfHondas更新Honda時,兩個實例中的值都會更新。

該方法與我們上面介紹的toRefs方法相似但又如此不同,因為它保持與源的連接,可用於字符串、數組和數字。 與toRefs不同,我們不需要擔心在創建時它的源中是否存在該屬性,因為如果在創建此ref時該屬性不存在而是返回null ,它仍然會被存儲作為一個有效的屬性,有一個watcher的形式,所以當這個值改變時,這個使用toRef創建的ref也會被更新。

我們也可以使用這個方法從props創建一個響應式屬性。 看起來像這樣:

 <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>

在這裡,我們創建了一個基於從props獲得的gender屬性的ref 。 當我們想要對特定組件的 prop 執行額外操作時,這會派上用場。

結論

在本文中,我們使用 Vue 3 中新引入的一些方法和函數來了解 Vue 中的反應性如何工作。我們首先了解什麼是反應性以及 Vue 如何在幕後使用Proxy對象來實現這一點。 我們還研究瞭如何使用reactive創建響應式對像以及如何使用ref創建響應式屬性。

最後,我們研究瞭如何將響應式對象轉換為普通對象,每個對象的屬性都是指向原始對象相應屬性的ref ,並且我們看到瞭如何為響應式源對像上的屬性創建ref

更多資源

  • “代理”(對象),MDN Web Docs
  • “反應性基礎”,Vue.js
  • “參考”,Vue.js
  • “Lifecycle Hook Registration Inside setup ”,Vue.js