vue2_$set

简介: vue2_$set

背景

动态修改对象的属性时,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())
    }
  }
}

image.png

结束

感谢阅读,如有不足或者错误欢迎指正!

目录
相关文章
|
JavaScript
vue2中$set的原理_它对object属性做了啥?
vue2中$set的原理_它对object属性做了啥?
140 1
|
2月前
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
241 1
|
6月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
168 2
|
3月前
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
98 0
|
3月前
|
存储 C++ 容器
unordered_set、unordered_multiset、unordered_map、unordered_multimap的介绍及使用
unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。在unordered_set中,元素的值同时也是唯一地标识它的key。在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。它的迭代器至少是前向迭代器。前向迭代器的特性。
177 0
|
3月前
|
编译器 C++ 容器
用一棵红黑树同时封装出map和set
再完成上面的代码后,我们的底层代码已经完成了,这时候已经是一个底层STL的红黑树了,已经已符合库里面的要求了,这时候我们是需要给他穿上对应的“衣服”,比如穿上set的“衣服”,那么这个穿上set的“衣服”,那么他就符合库里面set的要求了,同样map一样,这时候我们就需要实现set与map了。因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。我们就可以使用仿函数了。
43 0
|
3月前
|
存储 编译器 容器
set、map、multiset、multimap的介绍及使用以及区别,注意事项
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。set默认是升序的,但是其内部默认不是按照大于比较,而是按照小于比较。set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
191 0
|
7月前
|
编译器 容器
哈希表模拟封装unordered_map和unordered_set
哈希表模拟封装unordered_map和unordered_set