(一)需要
Vue 在非响应式的时候会有新增属性但并没有新增属性的状况。解决方案是 $set 从新设置下。
(二)介绍
1、Demo
<script>
const app = new Vue({
el: "#app",
data: {
user: {name: "test",},
},
mounted() {},
methods: {addAge() {
// this.user.age = 20 这样是不起作用, 不会被监听到
this.$set(this.user, "age", 20); // 应该应用
},
},
});
</script>
2、原理
vm.$set() 在 new Vue() 时就被注入到 Vue 原型上。
(1)入口
src/core/instance/index.js
import {initMixin} from './init'
import {stateMixin} from './state'
import {renderMixin} from './render'
import {eventsMixin} from './events'
import {lifecycleMixin} from './lifecycle'
import {warn} from '../util/index'
function Vue(options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
// 给 Vue 原型绑定三个实例办法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
(2)stateMixin 文件
src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data}
const propsDef = {}
propsDef.get = function () { return this._props}
if (process.env.NODE_ENV !== 'production') {dataDef.set = function () {
warn(
'Avoid replacing instance root $data.' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set // 在这里导入 set 办法到 $set 下面
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()}
return function unwatchFn () {watcher.teardown()
}
}
}
导入的入口
import {
set,
del,
observe,
defineReactive,
toggleObserving
} from '../observer/index'
(3)set 办法
src/core/observer/index.js
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
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)}`)
}
//target 为数组:调用 splice 办法
if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
//target 为对象,且 key 不是原型上的属性解决:间接批改
if (key in target && !(key in Object.prototype)) {target[key] = val
return val
}
//target 不能是 Vue 实例,或者 Vue 实例的根数据对象,否则报错
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
}
//target 是非响应式数据时,咱们就依照一般对象增加属性的形式来解决
if (!ob) {target[key] = val
return val
}
//target 为响应数据,且 **key 为新增属性 **,咱们 key 设置为响应式,并手动触发其属性值的更新
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
写在最初的话
学习路上,经常会懈怠。
《有想学技术须要监督的同学嘛~》
https://mp.weixin.qq.com/s/Fy…
如果有须要的搭档,能够加我微信:learningisconnecting
或者能够关注我的公众号:国星聊成长(我会分享成长的办法)