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

结束

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

目录
相关文章
|
7月前
|
JavaScript
vue2中$set的原理_它对object属性做了啥?
vue2中$set的原理_它对object属性做了啥?
76 1
|
15天前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
52 18
你对Collection中Set、List、Map理解?
|
8天前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
45 20
|
25天前
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
30 3
【C++】map、set基本用法
|
25天前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
21 5
|
2月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
3月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
47 6
【数据结构】map&set详解
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
43 1
|
3月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
42 5