轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
響應式系統(Reactivity systems)是現代前端框架的關鍵部分之一。應用系統的的高度互動性、動態性和響應能力全靠它支援。每個Web開發人員而言都應該瞭解這一系統的功能和實踐操作。
l 原理
響應系統是一種使自動使資料來源(模型)與資料表示(檢視)層自動保持同步的機制。每次模型更改時,都會重新渲染檢視。
以一個簡單的Markdown編輯器為例。通常編輯器有兩個窗格:一個窗格用於編寫Markdown程式碼(用於修改基礎模型),另一個窗格用於預覽已編譯的HTML(顯示已更新的檢視)。當我們在書寫窗格中寫東西時,它會立即在預覽窗格中自動預覽。這個例子比較簡單,在實際情況中會複雜很多。
在許多情況下,我們要顯示的資料取決於其他資料。在這種情況下,需要跟蹤相關資料,並根據跟蹤情況來更新資料。例如,我們有一個fullName,該屬性由firstName和lastName屬性組成。修改其任何依賴項後,fullName將自動重新評估,並在檢視中顯示結果。
瞭解什麼是響應式系統後,在瞭解Vue 3中的響應系統如何工作以及如何在實踐中使用之前,讓我們一起來快速回顧一下Vue 2中的響應系統內容及其注意事項。
l Vue 2的響應式系統簡介
Vue 2中的響應或多或少會被“隱藏”。無論我們放置在data物件中的是什麼,Vue都會使其隱式反應(reactive implicitly)。這樣雖然可以使開發人員的工作更加輕鬆,但靈活度卻會不可避免的降低。
在幕後,Vue 2使用ES5 Object.defineProperty將data物件的所有屬性轉換為getter和setter。對於每個元件例項,Vue建立一個依賴關係觀察程式例項,觀察者會記錄元件渲染期間依賴收集/跟蹤的任何屬性。當屬性觸發依賴項的設定器時,將通知觀察者,並將元件重新渲染並更新檢視。但是卻也會有一些問題存在。
n 變更檢測警告
由於Object.defineProperty方法的限制,Vue無法檢測到某些資料更改。包括:
- 給物件新增屬性或把物件移除屬性(例如obj.newKey = value)
- 按索引設定陣列項(例如arr[index] = newValue)
- 修改陣列的長度(例如arr.length = newLength)
不過為了解決這些問題, Vue為提供了Vue.set API方法,該方法向響應物件新增了一個屬性,確保新屬性也是響應性的,從而觸發了檢視更新。
用下述例項討論該情況:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="item, index in activities" :key="index"> {{ item }} <button @click="editActivity(index)">Edit</button> </li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
const App = new Vue({ el: '#app', data: { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] }, methods: { // 1. Add a new property to an object addAgeProperty() { this.person.age = 30 }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { this.activities[index] = newValue } }, // 3. Modifying the length of the array clearActivities() { this.activities.length = 0 } } });
在上面的示例中,我們會發現這三種方法都不起作用。我們不能向該person物件新增新屬性,無法使用activities的索引來編輯陣列中的專案,也不能修改activities陣列的長度。
優化如下:
const App = new Vue({ el: '#app', data: { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] }, methods: { // 1. Adding a new property to the object addAgeProperty() { Vue.set(this.person, 'age', 30) }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { Vue.set(this.activities, index, newValue) } }, // 3. Modifying the length of the array clearActivities() { this.activities.splice(0) } } });
在此示例中,我們用Vue.setAPI方法將新age屬性新增到person物件,並從活動陣列中選擇/修改特定專案。在最後一種情況下,使用JavaScript內建splice方法。
這個做法完全可行但卻略顯笨拙,而且會導致前後程式碼不一致。而Vue 3就解決了這個問題。
我們用下面示例繼續看:
const App = { data() { return { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] } }, methods: { // 1. Adding a new property to the object addAgeProperty() { this.person.age = 30 }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { this.activities[index] = newValue } }, // 3. Modifying the length of the array clearActivities() { this.activities.length = 0 } } } Vue.createApp(App).mount('#app')
可以看到在Vue 3中,所有方法都可以正常工作。
在Vue 2.6中,引入的Vue.observable API方法,一定程度的公開了響應式系統,使開發人員可以體驗到響應式系統的內容。實際上,這和Vue內部用來包裝data物件是完全相同的方法,對於在簡單場景建立小的跨元件狀態儲存很有用。但依舊沒辦法和Vue3的響應式系統相比,接下來就為大家詳細介紹。
注意:由於Object.defineProperty方法是僅限ES5且不可調整的功能,因此Vue 2不支援IE8及以下版本。
l Vue 3響應式系統如何工作
為了充分利用ES6 Proxy and Reflect API ,Vue 3中的響應式系統已被完全重寫。新版本新增響應式API,該API使系統比以前更加靈活和強大。
Proxy API允許開發人員攔截和修改目標物件上的更低階物件操作。代理(proxy)是物件的克隆/包裝(clone/wrapper),並提供特殊功能(稱為target),這些功能響應特定的操作並覆蓋JavaScript物件的內建行為(稱為traps)。如果仍然需要使用預設行為,則可以使用相應的Reflection API,其名稱顧名思義就是反映Proxy API的方法。這裡有一個示例,用來了解如何在Vue 3中使用這些API:
let person = { name: "David", age: 27 }; const handler = { get(target, property, receiver) { // track(target, property) console.log(property) // output: name return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { // trigger(target, property) console.log(`${property}: ${value}`) // output: "age: 30" and "hobby: Programming" return Reflect.set(target, property, value, receiver) } } let proxy = new Proxy(person, handler); console.log(person) // get (reading a property value) console.log(proxy.name) // output: David // set (writing to a property) proxy.age = 30; // set (creating a new property) proxy.hobby = "Programming"; console.log(person)
要建立一個新的代理,使用new Proxy(target, handler)建構函式。它帶有兩個引數:目標物件(person物件)和處理程式物件,該物件定義將攔截哪些操作(get和set操作)。在handler物件中, get和set陷阱來跟蹤何時讀取屬性以及何時修改/新增屬性。設定控制檯語句以確保執行正確。
在get和set陷阱採用下列引數:
- target:代理包裝的目標物件
- property:屬性名稱
- value:屬性值(此引數僅用於設定操作)
- receiver:進行操作的物件(通常是代理)
- Reflect API方法與其相應的代理方法接受相同的引數
註釋中track函式和trigger函式特定用於Vue,用於跟蹤何時讀取屬性以及何時修改/新增屬性。
在示例的最後一部分,用控制檯語句輸出原始person物件。然後用另一份宣告中讀取屬性name的proxy物件。接下來,修改age屬性並建立一個新hobby屬性。最後,再次輸出該物件以檢視它是否正確更新。
以上就是Vue3響應式系統的完整工作流程,但在實際工作中會複雜得多。
使用Vue 3響應式系統,還有一些注意事項:
- 僅適用於支援ES6 +的瀏覽器
- 響應代理不等於原始物件
l 總結
以上我們將Vue2和Vue3中響應式系統部分進行了比較,並對響應式系統的工作原理進行了說明,在後面的文章中,我們會進一步為大家介紹Vue3中響應式系統的API,敬請期待。