那些Vue開發遇到的坑---響應式系統

Vue是目前使用較爲廣泛的前端框架之一。相比React,Vue更容易學習上手。畢竟在React中萬物皆JavaScript。這讓一些習慣於編寫HTML+JavaScript的程序員不太樂於接受。相比之下,Vue的模板語法它不香麼。

當然,Vue同樣支持類似於JSX的語法的渲染函數,但是相信我,只要你學了模板語法,你就會放棄渲染函數。

有的同學可能會提到AngularJS,這裏就要說道,Vue的一些語法設計的確參考了AngularJS,但是Vue的API設計相對AngularJS要簡單的多,學習成本更低。

而且,Vue在設計過程中解決了很多AngularJS存在的問題,包括Vue對數據流的控制都會讓你的代碼更加清晰易懂,讓你可以在使用框架或者閱讀別人代碼的時候少說幾句F**k(這個不完全保證)。

 

 

雖然Vue上手容易,但這並不代表你可以輕而易舉的完全掌握它,要想真正瞭解並熟練這個框架,它的一些底層原理還是要了解一二的,這同樣有助於開闊你的編程思路。今天我們就先介紹一下Vue最獨特的特性之一--------響應式系統(這句話是抄官方的)。

Vue的響應式指的是你在一個頁面中展示了一個變量的值,當這個變量的值由於一些操作發生改變時,Vue會自動在無需刷新界面的前提下幫你把新的值展示到相應的位置,當然這個過程不需要你自己寫任何的dom刷新渲染的代碼(我覺得我說的夠通俗易懂了,再看不懂,請你去看尤雨溪的官方解說,保證你更看不懂)。

爲了實現這一效果,Vue做了很多你不知道的事(不然怎麼會不用你寫一行代碼,因爲他們替你寫了)。接下來的解說涉及一些Vue和JavaScript的基礎知識,比如Object.defineProperty等,不太瞭解的同學請複製Object.defineProperty並打開瀏覽器粘貼到檢索欄按下回車看看這到底是啥。

雖然Vue3版本棄用defineProperty改用proxy,但是響應式系統的主要思路還是沒有變的,所以此處提到的defineProperty是Vue2的實現方法,這點請小夥伴們注意哦。

 

 

 VUE·響應式原理

一個Vue實例具備一個名爲data的數據對象,對象中包含了當前Vue實例所需要的數據,當一個Vue實例生成時,Vue的響應式系統會遞歸的將data的property通過Object.defineProperty轉換爲getter/setter。

你可以理解爲響應式系統對每一個實例數據綁定了getter/setter函數,要獲取數據需要通過調用getter函數,爲數據寫入新值則需要調用setter函數。

每一個Vue實例還對應一個watcher實例(看名字就是知道這是拿來監聽的)。這個watcher實例會記錄與它對應的Vue實例的所接觸過的所有數據。在此之後如果某條數據發生改變,那麼必將通過setter函數去設置新值,這時watcher會監聽到這一變化,然後通知用到這個數據的Vue實例進行重新渲染,更新新值到頁面上,整個流程如下圖:

引自:https://cn.vuejs.org/v2/guide/reactivity.html

 

上面那段話可能會比較晦澀難懂,因此我準備了下面這段話:我們以一個按鈕爲例,按鈕上顯示了一個由變量定義的字,當點擊按鈕時按鈕上的文字會發生改變,代碼如下:

<template>    <div>        <button type="button" @click="message='Do not click me!'">{{message}}</button>    </div></template><script>  export default {    name: "demo",    data() {      return {        message: 'Click Me!'      }    }  }</script>

從代碼中我們可以看到,這個Vue實例包含一個按鈕和一個名爲message的數據,在按鈕上的字通過調用message來展示。

當這個Vue實例被註冊時,我們的響應式系統會爲message設置一對getter/setter函數,然後這個Vue實例會去一個叫做watcher的地方登記他用到的變量,這裏它登記的就是message,它告訴watcher,我用到了message,當他改變的時候請及時告訴我。Watcher就在小本本上記下來了,並且和message的getter/setter函數保持聯繫,當我們點擊按鈕,按鈕的click事件改變了message的值,這時會先調用setter函數,setter函數在改變message的值的同時會通知watcher,watcher收到這一消息之後就會通知Vue實例,告訴他,你用到的message變了,Vue實例收到這一消息就會重新渲染按鈕,把新的message值顯示在按鈕上,至此,一次響應式更新完成了。

 

 

那些VUE開發遇到的坑

 

響應式系統

Vue的響應式系統非常好用,開發者甚至可以不懂得DOM的渲染相關知識就能完成一個響應式頁面的開發,但是,我們日常開發總不可能是都像教程裏的demo一樣簡簡單單清清楚楚,一個龐大的web系統會有複雜的組件嵌套引用,組件之間有着複雜的數據交互,偶爾經常就會出現bug,而且有時候你在你的代碼中找不到任何問題(那是你以爲),然後就會百思不得其解爲什麼我的數據沒有及時更新到頁面上!!!一定是Vue有問題,破框架!!!!

好了,吐槽完之後我們還是老老實實看看,到底那裏出了問題,爲什麼你的代碼沒有按照預期的運行。

今天我就爲大家分析一下,在利用Vue進行開發的時候,爲什麼有些數據的變化不會被及時監聽到並觸發相關組件從新渲染。

對象類型在JavaScript中是一個引用類型,與基本類型不同,對象是按照引用訪問的。因此,如果你想在Vue中監聽到一下對象類型變量的變化時,你需要一些額外的操作,就比如下面這幾行代碼:

<template>    <div>        {{message.content}}        <button type="button" @click="message.content='clicked'">click message</button>    </div></template><script>  export default {    name: "demo",    data() {      return {        message: {}      }    }  }</script>

以上代碼渲染了一個按鈕,並且聲明瞭一個名爲message的空的對象變量,意圖是想要在點擊按鈕時,爲message對象設置contact屬性的值爲‘clicked’。當我們開始運行我們的代碼並在頁面上點擊按鈕時,頁面上並沒有按照我們預期的展示出message的content屬性值。然後作爲一個程序員,你可能就要開始打debugger一步一步的調試,然後你會發現,你的代碼並沒有寫錯,在調試器中,message的屬性確實改變了,並且按照預期被設置爲‘clicked’,但是,爲什麼頁面毫無反應,預期的‘clicked’字符串去哪裏了?

其實,這是由於Vue雖然在初始化的時候向watcher註冊了message, watcher中並沒有記錄一個後續添加的content屬性,除非你重新爲message賦值否則Vue是無法監聽到message的content屬性的。

Vue的開發者當然不可能這麼無情的讓你換個寫法,所以他們提供了一個set函數,這個函數可以保證你爲message添加的屬性也是響應式的,那麼就可以讓代碼按照你的要求執行了,具體實現如下:

<template>    <div>        {{message.content}}        <button type="button" @click="click">click message</button>    </div></template><script>  export default {    name: "demo",    data() {      return {        message: {}      }    },    methods:{      click(){        this.$set(this.message,'content','clicked')      }    }  }</script>

除了以上的情況,還有另外一種常見情形,就是當一個組件接收一些來自父組件的變量時,如果這個變量是一個對象,你同樣無法使用Vue實例的watch去監聽到他,在此情況下,可以通過設置deep爲true來實現對象的深度監聽,如下:

<template>    <div>        {{message.content}}    </div></template><script>  export default {    name: "demo",    props:{      message:{        default:null,      }    },    watch:{      message:{        deep:true,        handler(val){          //message發生改變後的一些邏輯處理        }      }    }  }</script>

值得提醒的是,數組類型在JavaScript中也是一個比較特殊的數據類型,與對象類型相似,數組也是引用類型,因此在開發中也會遇到和對象類型相似的問題,解決方法也是大同小異,此處就不多加說明。

 

 

關於作者夏夏,前端工程師,參與普元DevOps產品開發,以及微服務、容器雲等產品開發,負責前端頁面設計、架構搭建等工作。善於架構搭建、組件封裝及相關算法設計。

 

 

關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。長按二維碼關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章