背景
动态修改对象的属性时,vue2视图template不会跟新渲染的数据,来一探究竟
动态添加属性,不使用$set
测试案例:
config属性被我动态添加name:yma16
结论:
如果template动态添加则说明对象的属性添加删除具有响应式
<template>
<div class="container">
<h1>{
{
msg }}</h1>
<span>
config渲染:
</span>
<span>
{
{
config }}
</span>
<hr />
<div style="width: 600px;margin: 0 auto">
<span>
添加config属性
</span>
<el-form label-width="80px" :model="form" ref="formRef">
<el-form-item
label="key"
:rules="{ required: true, message: 'key不能为空!', trigger: 'blur' }"
prop="key"
>
<el-input v-model="form.key" placeholder="请输入key"></el-input>
</el-form-item>
<el-form-item label="value">
<el-input v-model="form.value" placeholder="请输入value"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">
添加保存
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Dashboard',
data () {
return {
msg: '$set用法',
config: {
key: 'value'
},
form: {
key: '',
value: ''
}
}
},
methods: {
submit () {
this.$refs['formRef'].validate().then(valid => {
console.log('valid', valid)
if (valid) {
this.config[this.form.key] = this.form.value
this.$message.success('添加属性成功!')
console.log('this.config', this.config)
} else {
this.$message.warning('请检查配置!')
}
})
}
}
}
</script>
vue2动态添加对象属性不具有响应式
动态添加属性,使用$set
改动点:
submit使用$set添加替换属性
<template>
<div class="container">
<h1>{
{
msg }}</h1>
<span>
config渲染:
</span>
<span>
{
{
config }}
</span>
<hr />
<div style="width: 600px;margin: 0 auto">
<span>
添加config属性
</span>
<el-form label-width="80px" :model="form" ref="formRef">
<el-form-item
label="key"
:rules="{ required: true, message: 'key不能为空!', trigger: 'blur' }"
prop="key"
>
<el-input v-model="form.key" placeholder="请输入key"></el-input>
</el-form-item>
<el-form-item label="value">
<el-input v-model="form.value" placeholder="请输入value"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">
添加保存
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'Dashboard',
data () {
return {
msg: '$set用法',
config: {
key: 'value'
},
form: {
key: '',
value: ''
}
}
},
methods: {
submit () {
this.$refs['formRef'].validate().then(valid => {
console.log('valid', valid)
if (valid) {
this.$set(this.config, this.form.key, this.form.value)
this.$message.success('添加属性成功!')
console.log('this.config', this.config)
} else {
this.$message.warning('请检查配置!')
}
})
}
}
}
</script>
vue2动态添加对象属性使用$set可以让对象具有响应式渲染
$set做了啥?
对象defineProperty
给对象添加属性
var myObject = {
};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 不可写!
configurable: true,
enumerable: true
} );
myObject.a = 3;
myObject.a; // 2
对象的Getter和Setter
object获取和配置属性(用get达实现监听)
定义新属性b设置为a的2倍,达到监听变量a变化的效果
当a变量地址改变时属性b也会改变
var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
};
Object.defineProperty(
myObject,
"b",
{
get: function(){
return this.a * 2 },
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4
$set的源码
vue的原型链挂了set
set里面定义属性且可枚举
使用了枚举enumerable属性和注册configurable属性
/**
* Define a reactive property on an Object.
*/
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if (
(!getter || setter) &&
(val === NO_INIITIAL_VALUE || arguments.length === 2)
) {
val = obj[key]
}
let childOb = !shallow && observe(val, false, mock)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
if (__DEV__) {
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
if (childOb) {
childOb.dep.depend()
if (isArray(value)) {
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
}
})
return dep
}
重点
当一个数据为响应式时,vue会给该数据添加一个ob属性,因此可以通过判断target对象是否存在ob属性来判断target是否是响应式数据
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
当target对象是响应式数据时,我们将target的属性key也设置为响应式并手动触发通知其属性值的更新
defineReactive(ob.value, key, val)
ob.dep.notify()
notify 触发update更新
承接$set的notify
notify(info?: DebuggerEventExtraInfo) {
// stabilize the subscriber list first
const subs = this.subs.filter(s => s) as DepTarget[]
if (__DEV__ && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
const sub = subs[i]
if (__DEV__ && info) {
sub.onTrigger &&
sub.onTrigger({
effect: subs[i],
...info
})
}
sub.update()
}
}
update 继承 DebuggerOptions
/**
* @internal
*/
export interface DepTarget extends DebuggerOptions {
id: number
addDep(dep: Dep): void
update(): void
}
onTrack和onTrigger!
watch update
_update更新渲染节点
function _update(oldVnode, vnode) {
const isCreate = oldVnode === emptyNode
const isDestroy = vnode === emptyNode
const oldDirs = normalizeDirectives(
oldVnode.data.directives,
oldVnode.context
)
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
const dirsWithInsert: any[] = []
const dirsWithPostpatch: any[] = []
let key, oldDir, dir
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
if (!oldDir) {
// new directive, bind
callHook(dir, 'bind', vnode, oldVnode)
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else {
// existing directive, update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
// update 更新 vnode
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}
if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
update的调用地方太多
找了一个监听的update
/**
* @internal since we are not exposing this in Vue 2, it's used only for
* internal testing.
*/
export function effect(fn: () => any, scheduler?: (cb: any) => void) {
const watcher = new Watcher(currentInstance, fn, noop, {
sync: true
})
if (scheduler) {
watcher.update = () => {
scheduler(() => watcher.run())
}
}
}
结束
感谢阅读,如有不足或者错误欢迎指正!