从Proxy到Vue3数据绑定
導言: 本菜雞在Vue2沒多久,結果就Vue3發布了。趕緊學習和體驗了一番Vue3,發現和Vue2有較大不同。其中最讓我印象深刻的是他們有一個叫ref和reactive的用來綁定和更新數據。然后再略微調查原理之后發現Vue3是用的Proxy來實現數據綁定。我之前對JS語法的Proxy基本完全不懂,正好趁這個機會,深入理解一下Proxy和Vue3的數據綁定。希望對于同樣是初學者的你,看完我的文章,能對這兩部分知識有更深入的認識。
數據綁定
我們進行網頁設計,除了需要設計各個頁面組件之外,另外最重要的部分應該就屬于數據交互了。經常我們要面對的需求是獲取用戶的數據或者將一些處理過的數據發送給用戶。比如我們用原生代碼展示一下上將數據發送給用戶:
<p></p> const data = { value: 'hello' } document.querySelector('p').innerText = data.value;等到我們使用Vue之后,Vue引入了一種設計模式稱為MVVM,我們不再和DOM直接進行交互。
MVVM是Model-View-ViewModel縮寫,也就是把MVC中的Controller演變成ViewModel。Model層代表數據模型,View代表UI組件,ViewModel是View和Model層的橋梁,數據會綁定到viewModel層并自動將數據渲染到頁面中,視圖變化的時候會通知viewModel層更新數據。
上圖是一個經驗的MVVM的示意圖,我們在使用Vue的時候,只設計UI以及數據處理的Model。如何將數據渲染在頁面上,我們不再關心了。交給Vue來處理。
可以再看一下,我們最開始舉得簡單代碼的例子。之前我們是View和Model直接進行通信的,現在多了一個ViewModel層。就像假設你之前出入小區可以隨便出入。現在因為疫情,門口多了一個站崗的保安。你能不能出入不再是由你決定了,而是由保安決定。這樣的保安或者ViewModel,可以稱之為一種代理。
類似于交易不是雙方直接達成,而是要經過中間商才能達成交易。類似于你們之前是直接現金交易,現在都需要用支付寶轉到對方賬戶才行。支付寶就是這樣的中間商或者稱為代理。有了代理就可以設置一些處理規則,或者攔截等。比如支付寶可以定義一種代理規則:只要是通過我轉賬到對方賬戶,我就要收取1%的手續費。
Proxy
我們理解了代理這個概念之間,下面就可以開始講Proxy。首先看一下MDN關于Proxy的定義:
Proxy 對象用于創建一個對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等)
總之,Proxy就是干代理的事。就是前文講的那個保安或者支付寶。
關于Proxy最簡單的講解,我就不去貼MDN的內容了,如果對Proxy具體語法完全不了解請移步Proxy,Proxy的handler對象方法雖然很多,但最最常用的還是get和set。如果完全不了解Proxy的,別的對象方法可以暫時不看,一定要把這兩個方法看一下。
下面我舉一些Proxy的通俗例子,并逐漸過渡到Vue3的Proxy中。
首先舉一個簡單的例子:
let o = {name: 'xiaoming',age: 20 }let handler = {get(obj, key) {return key in obj ? obj[key] : ''} }let p = new Proxy(o, handler)console.log(p.from)這個代碼是想表達如果 o 對象有這個 key-value 則直接返回,如果沒有一律返回空。這里的p的結果就相當于是一個代理過后的結果了,o的數據不是直接傳給p,而是經過經過handler代理邏輯之后才傳過去的。對于對象o而言,如果直接訪問o.from會報錯。對于p而言,因為中間有一個代理程序幫我們處理了一下,所以不會報錯,而是返回空。
通過上面的例子,我們初步了解了Proxy的代理特性,下面我們來看一下更復雜一點的例子。
let user = {name: 'xiaoming',age: 20,_password: '***' } user = new Proxy(user, {get(target, prop) {if (prop.startsWith('_')) {throw new Error('不可訪問')} else {return target[prop]}},set(target, prop, val) {if (prop.startsWith('_')) {throw new Error('不可訪問')} else {target[prop] = valreturn true}},deleteProperty(target, prop) { // 攔截刪除if (prop.startsWith('_')) {throw new Error('不可刪除')} else {delete target[prop]return true}},ownKeys(target) {return Object.keys(target).filter(key => !key.startsWith('_'))} }) console.log(user.age) console.log(user._password) user.age = 18 console.log(user.age) try {user._password = 'xxx' } catch (e) {console.log(e.message) }try {// delete user.agedelete user._password } catch (e) {console.log(e.message) } console.log(user.age)for (let key in user) {console.log(key) }大家可以執行一下代碼看看輸出效果。關于Proxy的基本介紹就先到這里,下面我們來看看Vue3中是如何使用Proxy實現數據綁定的。
Vue3中的Proxy
在Vue3中我們使用ref和reactive來實現數據綁定。更改ref或者reactive實例化對象的值,會導致頁面中的數據也發生對應更改。之所以能夠實現更改ref或者reactive對應的值使得頁面的值發生對應變化是因為我們實際操作的數據是經過Proxy代理過后的,而在Proxy代理中,我們有具體定義頁面數據刷新的代碼。我用代碼舉一個很簡單的例子:
let obj = {name: 'xiaoming', age: 20} let state = new Proxy(obj, {get (obj, key) {return obj[key]},set (obj, key, value) {obj[key] = valueconsole.log('更新UI界面')// set方法必須通過返回值告訴Proxy此次操作是否成功return true} })console.log(state.name) state.age = 22你可以執行一下上述代碼看看執行效果。當我們去設置一個值的時候,就會具體執行更能UI界面的操作。通過Proxy就可以實現ref和reactive的頁面更新和數據綁定,這也是ref和reactive背后的基本原理。
我們下面來談談reactive和ref的Proxy具體實現。
我們在使用Vue3的時候,通常使用ref來監聽一些簡單數據類型如數字、字符串、布爾等。你可能注意到一種現象,是更改ref的值的時候,需要使用ref.value的形式,(在template中使用ref時,Vue自動幫我們添加.value了,所以不需要我們手動添加),看起來很神奇,為什么會有一個.value呢?是因為ref本質上是一個包裝過后的reactive。在你去定義ref的時候,Vue會內部幫我們包裝一下。
let age = ref(18) // 等價于 let age = reactive({value: 18})對于reactive,使用Proxy來完成對象監聽要更復雜一些,因為對象里面可能還嵌套對象,比如下面這種形式的對象:
let arr = [{id:1, name: '魯班'}, {id:2, name: '虞姬'}] let obj = {a:{id:1, name: '魯班'}, b:{id:2, name: '虞姬'}}所以我們需要對每一層的對象都監聽到,才能做到當數據發生變化時,實現對應的更新。要實現一層一層的監聽,往往就會想到使用遞歸來實現。下面是代碼演示:
function reactive(obj) {if (typeof obj === 'object') {if (obj instanceof Array) {// 如果是一個數組,則取出數組中每一個元素,// 判斷每一個元素是否是一個對象,如果又是一個對象,則也需要包裝成Proxyobj.forEach((item, index) => {if (typeof item === 'object') {obj[index] = reactive(item)}})} else {// 如果是一個對象,則取出對象中的每一個屬性,// 判斷屬性是否又是一個對象,如果又是一個對象,則也需要包裝成Proxyfor (let key in obj) {let item = obj[key]if (typeof item === 'object') {obj[key] = reactive(item)}}}return new Proxy(obj, {get (obj, key) {return obj[key]},set (obj, key, val) {obj[key] = valconsole.log('更新UI界面')return true}})} else {console.warn(`${obj} is not object`)} }你可以執行一些測試看看效果,看看會輸出什么。
let state = reactive(arr) state[0].name = 'zhangsan' state[1].id = 3對于ref而言,因為ref是包裝過的reactive,所以使用reactive就可以定義ref:
function ref(val) {return reactive({value: val}) }參考資料
[1] Vue雙向數據綁定
[2] Vue面試題總結
[3] Proxy
[4] Vue3 的 Proxy 和 defineProperty 的比較
[5] Vue3.0教程
總結
以上是生活随笔為你收集整理的从Proxy到Vue3数据绑定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问题解决:vue dev模式没问题,di
- 下一篇: JavaScript为什么使用原型模式而