一文完全掌握Vue中的$set方法
start
今天在使用 $set 的時候,發(fā)現(xiàn)如果 被賦值的數(shù)據(jù) 層級較深會出現(xiàn)報錯的情況。
一知半解,是我最討厭的狀態(tài),今天就帶著問題,再閱讀一下對應(yīng)的源碼,了解問題的本質(zhì)。
問題說明
簡單說明一下我遇到的問題,明確探究問題的目標(biāo)。
需求
我有一個空對象,我希望可以給它的屬性的屬性的屬性賦值。
錯誤代碼:
<template> <div> lazy_tomato <h2>{{ obj }}</h2> <button @click="handleChange">點擊我給obj賦值</button> </div> </template> <script> export default { data() { return { obj: {}, } }, methods: { handleChange() { this.obj.a = { b: { c: '愛吃番茄', }, } console.log(JSON.stringify(this.obj)) // 直接新增屬性,不會觸發(fā) vue2本質(zhì)的Object.defineProperty。所以數(shù)據(jù)更新了視圖不更新 }, }, } </script>
正確代碼
<template> <div> lazy_tomato <h2>{{ obj }}</h2> <button @click="handleChange">點擊我給obj賦值</button> </div> </template> <script> export default { data() { return { obj: {}, } }, methods: { handleChange() { // 錯誤代碼二 typeError: Cannot read properties of undefined (reading '__ob__') // this.$set(this.obj.a, 'b.c', '愛吃番茄') // 正確代碼 this.$set(this.obj, 'a', { b: { c: '愛吃番茄' } }) console.log(JSON.stringify(this.obj)) }, }, } </script>
所以 $set 對這三個參數(shù)分別是如何處理的?如何避免我們錯誤使用?
區(qū)分 Vue.set 和 vm.$set
Vue
構(gòu)造函數(shù)自身上的 set
和 vm
實例上的 $set
是相同的函數(shù)。
解決了以下問題:
1.新增對象的屬性
2.刪除對象的屬性
3.通過數(shù)組索引修改數(shù)據(jù)
對應(yīng)源碼
完整源碼
export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { 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) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val }
分析源碼
// 1. 接受參數(shù)類型分別為 數(shù)組/對象; 任意 ; 任意 export function set(target: Array<any> | Object, key: any, val: any): any { // 2. 判斷第一個參數(shù) 不為 undefined null string number symbol boolean if ( process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn( `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}` ) } // 3. 如果是數(shù)組,而且第二個參數(shù)是有效索引 if (Array.isArray(target) && isValidArrayIndex(key)) { // 更新數(shù)組長度 有可能傳入的索引大于現(xiàn)有索引 target.length = Math.max(target.length, key) // 調(diào)用 splice target.splice(key, 1, val) // // 返回值是設(shè)置的值 return val } // 4. 是該對象的屬性 (且不是原型鏈上的屬性) if (key in target && !(key in Object.prototype)) { // 直接賦值 (這里賦值可以觸發(fā) Object.defineProperty) target[key] = val // 返回值是設(shè)置的值 return val } // 5. 獲取 observe實例 const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { 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 } // 6. 無observe實例,直接賦值,// 返回值是設(shè)置的值 if (!ob) { target[key] = val return val } // 7. 收集依賴 defineReactive(ob.value, key, val) // 8. 手動通知,觸發(fā)視圖更新 ob.dep.notify() // // 返回值是設(shè)置的值 return val } /* 工具函數(shù) */ function isPrimitive(value) { return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } // explicitness and function inlining. function isUndef(v) { return v === undefined || v === null } // 是否是有效的數(shù)組索引 function isValidArrayIndex(val) { const n = parseFloat(String(val)) return n >= 0 && Math.floor(n) === n && isFinite(val) }
小結(jié):
主要的處理順序:
- 處理數(shù)組(使用 劫持過的數(shù)組 splice 方法);
- 處理對象上自帶的屬性;
- 收集依賴,手動觸發(fā)。
// this.$set(this.obj.a, 'b.c', '愛吃番茄') 錯誤的原因,this.obj.a 本身是 undefined 所以直接被第一步就攔截了。 // this.obj.a={} // this.$set(this.obj.a, 'b.c', '愛吃番茄') 也達不到效果,它會直接吧 b.c當(dāng)做屬性名初始化
思考:
雖然官方文檔設(shè)定,第二個參數(shù)是數(shù)字和字符串,理論上可以傳入其他類型的。第二個參數(shù)最好是單層級的屬性值
擴展 :del 方法
/** * Delete a property and trigger change if necessary. * 如果需要,刪除屬性并觸發(fā)更改。 */ export function del(target: Array<any> | Object, key: any) { if ( process.env.NODE_ENV !== "production" && // 如果是 undefined 或 null; 或者是原始值 ---同Vue.$set (isUndef(target) || isPrimitive(target)) ) { warn( `Cannot delete reactive property on undefined, null, or primitive value: ${target}` ); } // 數(shù)組,利用splice,直接改 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return; } // ---同Vue.$set 排除Vue實例 和 根對象 const ob = (target: any).__ob__; if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== "production" && warn( "Avoid deleting properties on a Vue instance or its root $data " + "- just set it to null." ); return; } // 如果 屬性不是自身的屬性,直接 return if (!hasOwn(target, key)) { return; } // 刪除對應(yīng)的key delete target[key]; // 不是響應(yīng)式的不做處理(這個地方可以理解為,淺層監(jiān)聽的 watch,有些深層的屬性不需要watch,就會走這個情況) if (!ob) { return; } // 手動觸發(fā) ??! 有作者在想,直接在代碼中 `.__ob__` 手動通知不就ok了? 雖然可以但是不建議這樣做、 ob.dep.notify(); }
end
上述的演示,源碼查看的是 vue@2.6
。 vue3中由于響應(yīng)式實現(xiàn)原理發(fā)生了變化,所以不需要 $set 了,所以不做探究。
到此這篇關(guān)于完全掌握Vue中$set方法的文章就介紹到這了,更多相關(guān)Vue $set方法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ElementUI時間選擇器限制選擇范圍disabledData的使用
本文主要介紹了ElementUI時間選擇器限制選擇范圍disabledData的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06vue路由警告:Duplicate named routes definition問題
這篇文章主要介紹了vue路由警告:Duplicate named routes definition問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09vue中ref和$refs獲取元素dom、獲取子組件數(shù)據(jù)與方法調(diào)用示例
在Vue3中要獲取子組件的DOM節(jié)點,你可以使用ref來引用子組件,然后通過$refs來訪問子組件的DOM,下面這篇文章主要給大家介紹了關(guān)于vue中ref和$refs獲取元素dom、獲取子組件數(shù)據(jù)與方法調(diào)用的相關(guān)資料,需要的朋友可以參考下2024-07-07