一文完全掌握Vue中的$set方法
start
今天在使用 $set 的時候,發(fā)現(xiàn)如果 被賦值的數(shù)據(jù) 層級較深會出現(xiàn)報錯的情況。
一知半解,是我最討厭的狀態(tài),今天就帶著問題,再閱讀一下對應(yīng)的源碼,了解問題的本質(zhì)。
問題說明
簡單說明一下我遇到的問題,明確探究問題的目標。
需求
我有一個空對象,我希望可以給它的屬性的屬性的屬性賦值。
錯誤代碼:
<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當做屬性名初始化
思考:
雖然官方文檔設(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的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-06-06
vue路由警告:Duplicate named routes definition問題
這篇文章主要介紹了vue路由警告:Duplicate named routes definition問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
vue中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

