摘要:響應(yīng)式原理為了探究這一切的原因,我再次點(diǎn)開了的官網(wǎng)。在官網(wǎng)很下面的位置,找到了關(guān)于響應(yīng)式原理的說明。因此,新添加到數(shù)組中的對(duì)象中的屬性,就成了非響應(yīng)式的屬性了,改變它自然不會(huì)讓組件重新渲染。響應(yīng)式屬性的對(duì)象,有這個(gè)對(duì)象就代表是響應(yīng)式的。
??最近在用Vue開發(fā)一個(gè)后臺(tái)管理的demo,有一個(gè)非常常規(guī)的需求。然而這個(gè)常規(guī)的需求中,包含了大量的知識(shí)點(diǎn)。有一個(gè)產(chǎn)品表格,用來(lái)顯示不同產(chǎn)品的信息。然后表格要有一個(gè)內(nèi)嵌編輯的功能,點(diǎn)擊操作欄的編輯按鈕,對(duì)應(yīng)行的信息列就變成輸入框。第一版的代碼大致上像這樣。
{{scope.row.description}} 編輯
??邏輯很簡(jiǎn)單,我在表格數(shù)據(jù)數(shù)組中,給每一個(gè)對(duì)象都加入一個(gè)初始值為false的屬性"edit",然后根據(jù)這個(gè)屬性的值,使用v-show來(lái)決定渲染的是文本還是輸入框,是“編輯”還是“保存”。
??然而運(yùn)行起來(lái)之后的表現(xiàn)并不是像我想的一樣,事實(shí)上,點(diǎn)擊編輯按鈕后,對(duì)應(yīng)產(chǎn)品的“產(chǎn)品描述”并沒有變成輸入框,編輯按鈕也沒有變成保存按鈕。而我通過vue-devtool查看數(shù)據(jù)發(fā)現(xiàn),事實(shí)上對(duì)應(yīng)的edit屬性確實(shí)已經(jīng)變了,只是頁(yè)面上的組件沒有正確渲染。這讓我很困惑,說好的雙向綁定呢,為什么model層上的變化沒有響應(yīng)到view層上呢。
??首先,由于頁(yè)面初始顯示是正確的,把edit的初始值改成true后,也會(huì)有輸入框出現(xiàn),所以肯定不是代碼邏輯的問題。當(dāng)我試著把v-show的判斷條件改成數(shù)組中的對(duì)象原本就有的屬性時(shí),發(fā)現(xiàn)編輯狀態(tài)的切換突然變得正常了。而一旦我把判斷條件改回后來(lái)插入的edit時(shí),一切又變得不正常了。因此我推測(cè),一定是數(shù)據(jù)綁定出了什么問題。
??我在網(wǎng)上查了一下,有些類似的問題,大多數(shù)的解決方案是,給el-table加上一個(gè)隨機(jī)數(shù)key值:key="Math.random()"。試了一下,發(fā)現(xiàn)真的有用。之所以有用是因?yàn)?,每次?duì)這個(gè)表格有操作,key值都會(huì)變,這就相當(dāng)于產(chǎn)生了一個(gè)新的table,瀏覽器就會(huì)根據(jù)model層的數(shù)據(jù)重新渲染,這時(shí)候顯示當(dāng)然就正確了。但可想而知,這樣也會(huì)造成極大的性能浪費(fèi),而且這也沒有解決數(shù)據(jù)綁定的問題。
??我又試著對(duì)代碼做了一些修改。我把map和賦值操作放到了同一句里面去,代碼變成了這樣
this.$store.dispatch(GET_PRODUCTS).then(() => { this.products = this.$store.getters.products.map((item: any) => { item.edit = false; return item; }); });
神奇的事發(fā)生了,居然一切都恢復(fù)正常了。那么我就知道了,問題出在了數(shù)組和map函數(shù)上。
響應(yīng)式原理??為了探究這一切的原因,我再次點(diǎn)開了Vue的官網(wǎng)。在官網(wǎng)很下面的位置,找到了關(guān)于響應(yīng)式原理的說明。這張圖很好地說明了Vue實(shí)現(xiàn)雙向綁定的原理。
??當(dāng)一個(gè)javscript對(duì)象傳入Vue實(shí)例的data中時(shí),Vue會(huì)遍歷該對(duì)象的所有屬性,同時(shí)使用?Object.defineProperty方法將這些屬性全都轉(zhuǎn)成?getter/setter每個(gè)組件實(shí)例都對(duì)應(yīng)一個(gè)watcher實(shí)例,它會(huì)在組件渲染的過程中把“接觸”過的數(shù)據(jù)屬性記錄為依賴。之后當(dāng)依賴項(xiàng)的數(shù)據(jù)發(fā)生變化,也就是setter觸發(fā)時(shí),會(huì)通知watcher,從而使它關(guān)聯(lián)的組件重新渲染。
??而由于javascript的限制,Vue不能檢測(cè)到對(duì)象的添加或者刪除。并且Vue在初始化實(shí)例時(shí)就對(duì)屬性執(zhí)行了setter/getter轉(zhuǎn)化過程,所以屬性必須開始就在對(duì)象上,這樣才能讓Vue轉(zhuǎn)化它。而動(dòng)態(tài)添加的根級(jí)別的屬性,則不會(huì)轉(zhuǎn)化成響應(yīng)式的屬性。也就是說,往已經(jīng)創(chuàng)建的實(shí)例上添加的根級(jí)別的屬性,都會(huì)是非響應(yīng)式的。但是,可以使用 Vue.set(object, propertyName, value) 或者vm.$set(object, propertyName, value)方法向嵌套對(duì)象添加響應(yīng)式屬性。
??這里,數(shù)組相關(guān)的注意事項(xiàng)被額外提了出來(lái)。由于 JavaScript 的限制,Vue 不能檢測(cè)以下數(shù)組的變動(dòng):
當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue
當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:vm.items.length = newLength
??解決方法也很簡(jiǎn)單,使用上面提到的set方法就可以解決這個(gè)問題。與此同時(shí),官網(wǎng)上還有一段專門針對(duì)數(shù)組的變異方法的說明。
??所謂的變異方法,顧名思義,會(huì)改變調(diào)用了這些方法的原始數(shù)組。相比之下,也有非變異 (non-mutating method) 方法,例如 filter()、concat() 和 slice() 。它們不會(huì)改變?cè)紨?shù)組,而總是返回一個(gè)新數(shù)組。當(dāng)使用非變異方法時(shí),可以用新數(shù)組替換舊數(shù)組。并且,Vue還非常智能的會(huì)對(duì)于沒有變化的dom進(jìn)行重用,并不會(huì)整個(gè)進(jìn)行更新。
??看到這兒,我終于找到問題的關(guān)鍵在哪兒了。其實(shí)網(wǎng)上的各種說法都不準(zhǔn)確,真正出問題的點(diǎn)在于map函數(shù)的使用上。map是一個(gè)非變異方法,方法本身并不會(huì)改變?cè)瓟?shù)組,而是會(huì)返回一個(gè)新數(shù)組。因此,Vue并沒有對(duì)map方法進(jìn)行包裝,而是建議替換原數(shù)組。然而我在用的時(shí)候并沒有注意到這一點(diǎn),在使用的時(shí)候利用指針特性,把map方法當(dāng)做變異方法來(lái)用,直接改變?cè)瓟?shù)組,這自然就不會(huì)被Vue檢測(cè)到了。因此,新添加到數(shù)組中的對(duì)象中的edit屬性,就成了非響應(yīng)式的屬性了,改變它自然不會(huì)讓組件重新渲染。
原理都已經(jīng)搞清楚了,接下來(lái)我總結(jié)了一下這類數(shù)組問題的幾種解決方法。
??在el-table標(biāo)簽上添加:key="Math.random()",不管做了什么,都強(qiáng)制刷新整個(gè)表格,非常不推薦,極大的性能消耗。
??在使用數(shù)組方法的時(shí)候,分清變異方法和非變異方法,用非變異方法的時(shí)候,要用新數(shù)組替代舊數(shù)組,而不是直接變換原數(shù)組。
??我在"vue/src/core/observer/index.js"中找到了set方法的源碼。我們發(fā)現(xiàn)set函數(shù)接收三個(gè)參數(shù)分別為 target、key、val,其中target的值為數(shù)組或者對(duì)象,這正好和官網(wǎng)給出的調(diào)用Vue.set()方法時(shí)傳入的參數(shù)參數(shù)對(duì)應(yīng)上。然后往下看實(shí)現(xiàn),我基本上給每一行都加上了注釋。
export function set (target: Array| Object, key: any, val: any): any { if (process.env.NODE_ENV !== "production" && (isUndef(target) || isPrimitive(target)) ) {//判斷target的類型是否符合要求,若不符合要求,且不在生產(chǎn)環(huán)境下,就拋出警告。 warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) {//如果target是數(shù)組,且key值合法 target.length = Math.max(target.length, key) target.splice(key, 1, val)//用包裝好的變異方法splice進(jìn)行賦值。 return val } if (key in target && !(key in Object.prototype)) {//如果key是target中原有的屬性,就直接賦值。 target[key] = val return val } const ob = (target: any).__ob__//響應(yīng)式屬性的observer對(duì)象,有這個(gè)對(duì)象就代表是響應(yīng)式的。 if (target._isVue || (ob && ob.vmCount)) {//如果當(dāng)前的target對(duì)象是vue實(shí)例對(duì)象或者是根數(shù)據(jù)對(duì)象,就拋出警告。 process.env.NODE_ENV !== "production" && warn( "Avoid adding reactive properties to a Vue instance or its root $data " + "at runtime - declare it upfront in the data option." ) return val } if (!ob) {//如果不存在observer,那就不是響應(yīng)式對(duì)象,直接賦值。 target[key] = val return val } defineReactive(ob.value, key, val)//給新屬性添加依賴,以后直接修改屬性就能重新渲染。 ob.dep.notify()//直接觸發(fā)依賴。 return val }
可以看到,set方法對(duì)于數(shù)組的處理其實(shí)非常簡(jiǎn)單,就是調(diào)用了包裝好的splice方法。那么再來(lái)看一下包裝Array變異方法的代碼實(shí)現(xiàn),我同樣給每一行加上了注釋。其實(shí)做的事情也不多,主要就是給每個(gè)新添加的元素都加上觀察者。
... methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]//保存原方法。 def(arrayMethods, method, function mutator (...args) {//修改方法映射,調(diào)用數(shù)組方法的時(shí)候?qū)嶋H上調(diào)用的是對(duì)應(yīng)的mutator方法。 const result = original.apply(this, args)//調(diào)用原方法,先把結(jié)果求出來(lái) const ob = this.__ob__//獲取observer let inserted switch (method) { case "push": case "unshift": inserted = args break case "splice": inserted = args.slice(2) break }//對(duì)于往數(shù)組中加元素的方法,獲得添加的元素。 if (inserted) ob.observeArray(inserted)//給添加的元素添加觀察者。 // notify change ob.dep.notify()//觸發(fā)依賴。 return result }) })
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105707.html
摘要:響應(yīng)式原理之不論如何,最終響應(yīng)式數(shù)據(jù)都要通過來(lái)實(shí)現(xiàn),實(shí)際要借助新增的。在函數(shù)內(nèi),首先實(shí)例化一個(gè)實(shí)例,會(huì)在稍后添加為響應(yīng)式數(shù)據(jù)自定義的中發(fā)揮作用。只有數(shù)組和對(duì)象才可能是響應(yīng)式,才能返回實(shí)例。參考鏈接技術(shù)內(nèi)幕揭開數(shù)據(jù)響應(yīng)系統(tǒng)的面紗源碼 Vue響應(yīng)式原理之defineReactive defineReactive 不論如何,最終響應(yīng)式數(shù)據(jù)都要通過defineReactive來(lái)實(shí)現(xiàn),實(shí)際要借助...
摘要:響應(yīng)式原理之之前簡(jiǎn)單介紹了和類的代碼和作用,現(xiàn)在來(lái)介紹一下類和。對(duì)于數(shù)組,響應(yīng)式的實(shí)現(xiàn)稍有不同。不存在時(shí),說明不是響應(yīng)式數(shù)據(jù),直接更新。如果對(duì)象是響應(yīng)式的,確保刪除能觸發(fā)更新視圖。 Vue響應(yīng)式原理之Observer 之前簡(jiǎn)單介紹了Dep和Watcher類的代碼和作用,現(xiàn)在來(lái)介紹一下Observer類和set/get。在Vue實(shí)例后再添加響應(yīng)式數(shù)據(jù)時(shí)需要借助Vue.set/vm.$se...
摘要:淺析響應(yīng)式原理一的特點(diǎn)之一是響應(yīng)式,視圖隨著數(shù)據(jù)的更新而更新,在視圖中修改數(shù)據(jù)后實(shí)例中的數(shù)據(jù)也會(huì)同步更新。對(duì)于每個(gè)響應(yīng)式數(shù)據(jù),會(huì)有兩個(gè)實(shí)例,第一個(gè)是在中的閉包遍歷,用途顯而易見。接收一個(gè)回調(diào)函數(shù),會(huì)在重新求值且值更新后執(zhí)行。 淺析Vue響應(yīng)式原理(一) Vue的特點(diǎn)之一是響應(yīng)式,視圖隨著數(shù)據(jù)的更新而更新,在視圖中修改數(shù)據(jù)后Vue實(shí)例中的數(shù)據(jù)也會(huì)同步更新。內(nèi)部借助依賴(下文中的Dep類)...
摘要:前言最近在學(xué)習(xí)計(jì)算屬性的源碼,發(fā)現(xiàn)和普通的響應(yīng)式變量?jī)?nèi)部的實(shí)現(xiàn)還有一些不同,特地寫了這篇博客,記錄下自己學(xué)習(xí)的成果文中的源碼截圖只保留核心邏輯完整源碼地址可能需要了解一些響應(yīng)式的原理版本計(jì)算屬性的概念一般的計(jì)算屬性值是一個(gè)函數(shù),這個(gè)函數(shù)showImg(https://user-gold-cdn.xitu.io/2019/5/6/16a8b98f1361f6f6); 前言 最近在學(xué)習(xí)Vue計(jì)...
摘要:淺析的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),并不會(huì)立即更新。盡管已經(jīng)更新,但新增的元素并不立即插入到中。實(shí)際在中,執(zhí)行了,這也是自動(dòng)綁定到執(zhí)行上下文的原因。在內(nèi),使用數(shù)組保存回調(diào)函數(shù),表示當(dāng)前狀態(tài),使用函數(shù)來(lái)執(zhí)行回調(diào)隊(duì)列。 Vue.nextTick 淺析 Vue 的特點(diǎn)之一就是響應(yīng)式,但數(shù)據(jù)更新時(shí),DOM 并不會(huì)立即更新。當(dāng)我們有一個(gè)業(yè)務(wù)場(chǎng)景,需要在 DOM 更新之后再執(zhí)行一段代碼時(shí),可以...
閱讀 926·2021-10-18 13:32
閱讀 3528·2021-09-30 09:47
閱讀 2169·2021-09-23 11:21
閱讀 1894·2021-09-09 09:34
閱讀 3494·2019-08-30 15:43
閱讀 1533·2019-08-30 11:07
閱讀 1072·2019-08-29 16:14
閱讀 738·2019-08-29 11:06