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 中, props
、 computed
和data()
默認情況下都是響應式的,但創建此類組件時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
函數接受兩個參數: props
和context
。 這個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
方法創建了一個響應式對象,具有Toyota
和Honda
屬性。 我們還利用toRef
從Honda
創建了一個反應變量。 從上面的示例中,我們可以看到,當我們使用響應式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