Day 46/100 Vue this.$set原理

(一)需求

Vue 在非响应式的时候会有新增属性但并没有新增属性的情况。解决方案是$set 重新设置下。

(二)介绍

1、Demo

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) {
  // 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 | 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
或者可以关注我的公众号:国星聊成长(我会分享成长的方法)

你可能感兴趣的:(Day 46/100 Vue this.$set原理)