最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue源码解读(响应式原理介绍和Prop)

    正文概述 掘金(Husky-Yellow)   2021-02-28   383

    系列文章:

    • Vue源码解读(设计篇)
    • Vue源码解读(Rollup篇)
    • Vue源码解读(入口到构造函数整体流程)

    介绍

    分析过initState()方法的整体流程,知道它会处理propsmethodsdata等等相关的内容:

    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    

    那么深入响应式原理介绍会以initState()方法开始,逐步分析Vue中响应式的原理,下面这张图可以很好的展示响应式的原理。

    Vue源码解读(响应式原理介绍和Prop)

    前置核心概念

    Object.defineProperty介绍

    也许你已经从很多地方了解到,Vue.js利用了Object.defineProperty(obj, key, descriptor)方法来实现响应式,其中Object.defineProperty()方法的参数介绍如下:

    • obj:要定义其属性的对象。
    • key:要定义或修改属性的名称。
    • descriptor:要定义或修改属性的描述符。

    其中descriptor有很多可选的键值, 然而对Vue响应式来说最重要的是getset方法,它们分别会在获取属性值触发getter和设置属性值的时候触发setter。在介绍原理之前,来使用Object.defineProperty()来实现一个简单的响应式例子:

    function defineReactive (obj, key, val) {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          console.log('get msg')
          return val
        },
        set: function reactiveSetter (newVal) {
          console.log('set msg')
          val = newVal
        }
      })
    }
    const vm = {
      msg: 'hello, Vue.js'
    }
    let msg = ''
    defineReactive(vm, 'msg', vm.msg)
    msg = vm.msg          // get msg
    vm.msg = 'Hello, Msg' // set msg
    msg = vm.msg          // get msg
    

    为了在别的地方方便的使用Object.defineProperty()方法,把其封装成一个defineReactive函数。

    proxy代理

    在开发过程中,经常会直接使用this.xxx的形式直接访问props或者data中的值,这是因为Vuepropsdata默认做了proxy代理。关于什么是proxy代理,请先看一个简单的例子:

    this._data = {
      name: 'AAA',
      age: 23
    }
    // 代理前
    console.log(this._data.name) // AAA
    proxy(vm, '_data', key)
    // 代理后
    console.log(this.name)       // AAA
    

    接下来详细介绍proxy()方法是如何实现的,在instance/state.js文件中定义了proxy方法,它的代码也很简单:

    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    

    可以从上面的代码中发现,proxy方法主要是做了属性的getset方法劫持。

    const name = this.name
    this.name = 'BBB'
    // 等价于
    const name = this._data.name
    this._data.name = 'BBB'
    

    $options属性

    在之前的介绍中, 知道当 初始化Vue实例的时候传递的options会根据不同的情况进行配置合并,关于具体的options合并策略我们会在之后的章节详细介绍,现阶段我们只需要知道$options可以拿到合并后的所有属性,例如propsmethods以及data等等。

    假设定义了如下实例:

    const vm = new Vue({
      el: '#app',
      props: {
        msg: ''
      },
      data () {
        return {
          firstName: 'AAA',
          lastName: 'BBB',
          age: 23
        }
      },
      methods: {
        sayHello () {
          console.log('Hello, Vue.js')
        }
      },
      computed: {
        fullName () {
          return this.firstName + this.lastName
        }
      }
    })
    

    那么在之后可以通过下面的方式来取这些属性。

    const opts = this.$options
    const props = opts.props
    const methods = opts.methods
    const data = opts.data
    const computed = opts.computed
    const watch = opts.watch
    // ...等等
    

    props处理

    介绍完以上前置核心概念后,第一个要学习的就是Vue.js是如何处理与props相关的逻辑的。把与props相关的逻辑主要分成三个部分,分别是props规范化、props初始化和props更新。

    props规范化

    在了解规范化之前,先来列举一下在日常的开发过程中,主要有如下几种撰写组件props的方式:

    • 数组形式:props可以写成一个数组,但数组中的key元素必须为string类型。
    export default {
      props: ['name', 'age']
    }
    
    • 键值不为对象:此种方式常见于只需要定义key类型的props
    export default {
      props: {
        name: String
      }
    }
    
    • 规范格式:此种方式是Vue.js接受props最好的格式,对于一个有很高要求的组件来说,它通过会撰写很严格的props规则,这在各个开源UI框架中是最常见的。
    export default {
      props: {
        name: {
          type: String,
          default: ''
        },
        age: {
          type: Number,
          default: 0,
          validator (value) {
            return value >= 0 && value <= 100
          }
        }
      }
    }
    

    props规范化所做的事情,就是把各种不是规范格式的形式,规范化为规范格式,方便Vue.js在后续的过程中处理props。那么接下来,就来分析Vue.js是如何对props规范化的。

    props规范化的过程发生在this._init()方法中的mergeOptions合并配置中:

    import { mergeOptions } from '../util/index'
    export function _init (Vue) {
      const vm = this
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    

    其中mergeOptions()方法是定义在src/core/util/options.js文件中,它在其中有一段这样的方法调用:

    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
      // 省略代码
      normalizeProps(child, vm)
      return options
    } 
    

    可以发现,规范化props的代码,主要集中在normalizeProps()方法中,那么接下来详细分析normalizeProps()方法:

    function normalizeProps (options: Object, vm: ?Component) {
      const props = options.props
      if (!props) return
      const res = {}
      let i, val, name
      if (Array.isArray(props)) {
        i = props.length
        while (i--) {
          val = props[i]
          if (typeof val === 'string') {
            name = camelize(val)
            res[name] = { type: null }
          } else if (process.env.NODE_ENV !== 'production') {
            warn('props must be strings when using array syntax.')
          }
        }
      } else if (isPlainObject(props)) {
        for (const key in props) {
          val = props[key]
          name = camelize(key)
          res[name] = isPlainObject(val)
            ? val
            : { type: val }
        }
      } else if (process.env.NODE_ENV !== 'production') {
        warn(
          `Invalid value for option "props": expected an Array or an Object, ` +
          `but got ${toRawType(props)}.`,
          vm
        )
      }
      options.props = res
    }
    

    为了更好的理解normalizeProps()方法,来撰写几个案例来详细说明:

    • 数组形式:当props是数组时,会首先倒序遍历这个数组,然后使用typeof来判断数组元素的类型。如果不是string类型,则在开发环境下报错,如果是string类型,则先把key转化为驼峰形式,然后把这个key赋值到临时的res对象中,此时的键值固定为{ type: null }
    // 规范化前
    export default {
      props: ['age', 'nick-name']
    }
    
    // 规范化后
    export default {
      props: {
        age: {
          type: null
        },
        nickName: {
          type: null
        }
      }
    }
    
    • 对象形式:当为对象时会使用for-in遍历对象,紧接着和数组形式一样使用camelize来把key转成驼峰形式,然后使用isPlainObject()方法来判断是否为普通对象。如果不是,则转成{ type: Type }对象形式,其中Type为定义key时的Type,如果是,则直接使用这个对象。
    // 规范化前
    export default {
      props: {
        name: String,
        age: Number
      }
    }
    
    // 规范化后
    export default {
      props: {
        name: {
          type: String
        },
        age: {
          type: Number
        }
      }
    }
    
    • 既不是数组形式也不是对象形式:报错
    // 报错:Invalid value for option "props": expected an Array or an Object,but got String
    export default {
      props: 'name, age'
    }
    

    props初始化

    在了解了props规范化后,紧接着来了解一下props初始化的过程。props初始化过程同样是发生在this._init()方法中,它在initState的时候被处理:

    export function initState (vm) {
      // 省略代码
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
    }
    

    然后来详细看一下initProps中的代码:

    function initProps (vm: Component, propsOptions: Object) {
      const propsData = vm.$options.propsData || {}
      const props = vm._props = {}
      const keys = vm.$options._propKeys = []
      const isRoot = !vm.$parent
      if (!isRoot) {
        toggleObserving(false)
      }
      for (const key in propsOptions) {
        keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        if (process.env.NODE_ENV !== 'production') {
          const hyphenatedKey = hyphenate(key)
          if (isReservedAttribute(hyphenatedKey) ||
              config.isReservedAttr(hyphenatedKey)) {
            warn(
              `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
              vm
            )
          }
          defineReactive(props, key, value, () => {
            if (!isRoot && !isUpdatingChildComponent) {
              warn(
                `Avoid mutating a prop directly since the value will be ` +
                `overwritten whenever the parent component re-renders. ` +
                `Instead, use a data or computed property based on the prop's ` +
                `value. Prop being mutated: "${key}"`,
                vm
              )
            }
          })
        } else {
          defineReactive(props, key, value)
        }
        if (!(key in vm)) {
          proxy(vm, `_props`, key)
        }
      }
      toggleObserving(true)
    }
    

    在仔细阅读initProps()方法后,可以对initProps()方法进行总结,它主要做三件事情:props校验和求值props响应式props代理

    props响应式

    先来看看最简单的props响应式,这部分的过程主要使用了在之前介绍过的defineReactive方法:

    defineReactive(props, key, value, () => {
      if (!isRoot && !isUpdatingChildComponent) {
        warn(
          `Avoid mutating a prop directly since the value will be ` +
          `overwritten whenever the parent component re-renders. ` +
          `Instead, use a data or computed property based on the prop's ` +
          `value. Prop being mutated: "${key}"`,
          vm
        )
      }
    })
    

    唯一值得注意的地方就是:在开发环境下,props的响应式劫持了setter方法,这样做的是为了保证props为单项数据流:既不能在子组件中直接修改父组件传递的props值。

    props代理

    经过props响应式后,会在实例上得到this._props对象,为了方便更好的获取props的值,需要对props做一层proxy代理。关于proxy的实现,已经在之前的章节中介绍过了。

    this._props = {
      name: '',
      age: 0
    }
    
    // 代理前
    console.log(this._props.name)
    proxy(vm, `_props`, key)
    // 代理后
    console.log(this.name)
    

    props校验求值

    最后来看稍微复杂一点的props校验求值,这部分的功能发生在validateProp,它的代码如下:

    export function validateProp (
      key: string,
      propOptions: Object,
      propsData: Object,
      vm?: Component
    ): any {
      const prop = propOptions[key]
      const absent = !hasOwn(propsData, key)
      let value = propsData[key]
      // boolean casting
      const booleanIndex = getTypeIndex(Boolean, prop.type)
      if (booleanIndex > -1) {
        if (absent && !hasOwn(prop, 'default')) {
          value = false
        } else if (value === '' || value === hyphenate(key)) {
          // only cast empty string / same name to boolean if
          // boolean has higher priority
          const stringIndex = getTypeIndex(String, prop.type)
          if (stringIndex < 0 || booleanIndex < stringIndex) {
            value = true
          }
        }
      }
      // check default value
      if (value === undefined) {
        value = getPropDefaultValue(vm, prop, key)
        // since the default value is a fresh copy,
        // make sure to observe it.
        const prevShouldObserve = shouldObserve
        toggleObserving(true)
        observe(value)
        toggleObserving(prevShouldObserve)
      }
      if (
        process.env.NODE_ENV !== 'production' &&
        // skip validation for weex recycle-list child component props
        !(__WEEX__ && isObject(value) && ('@binding' in value))
      ) {
        assertProp(prop, key, value, vm, absent)
      }
      return value
    }
    

    代码分析:可以从以上代码中发现,validateProp虽然说的是带有校验的功能,但它并不会抛出错误进而阻止validateProp()方法返回value,而是根据校验的过程中的不同情况尽可能的提示出很清晰的提示。实质上validateProp()方法最主要的还是返回value,同时也根据不同的props写法处理不同的情况。可以将validateProp()方法进行总结,它主要做如下几件事情:

    • 处理Boolean类型的props
    • 处理default默认数据。
    • props断言。

    那么接下来将分别对这几件事情进行详细的描述。

    处理Boolean类型

    先来看几个props传递Boolean的例子:

    // Component A
    export default {
      props: {
        fixed: Boolean
      }
    }
    
    // Component B
    export default {
      props: {
        fixed: [Boolean, String]
      }
    }
    
    // Component C
    export default {
      props: {
        fixed: []
      }
    }
    

    然后回到源码中处理Boolean类型getTypeIndex的地方,这个函数的代码如下:

    function getTypeIndex (type, expectedTypes): number {
      if (!Array.isArray(expectedTypes)) {
        return isSameType(expectedTypes, type) ? 0 : -1
      }
      for (let i = 0, len = expectedTypes.length; i < len; i++) {
        if (isSameType(expectedTypes[i], type)) {
          return i
        }
      }
      return -1
    }
    

    这个函数的实现逻辑比较清晰:

    1. Component A组件为例,它的props不是一个数组但却是Boolean类型,因此返回索引0
    2. Component B组件为例,因为它的props都是一个数组,所以要遍历这个数组,然后返回Boolean类型在数组中的索引i
    3. Component C组件为例,虽然它是一个数组,但数组中没有任何元素,因此返回索引-1

    在拿到booleanIndex后,需要走下面这段代码逻辑:

    const booleanIndex = getTypeIndex(Boolean, prop.type)
    if (booleanIndex > -1) {
      if (absent && !hasOwn(prop, 'default')) {
        value = false
      } else if (value === '' || value === hyphenate(key)) {
        // only cast empty string / same name to boolean if
        // boolean has higher priority
        const stringIndex = getTypeIndex(String, prop.type)
        if (stringIndex < 0 || booleanIndex < stringIndex) {
          value = true
        }
      }
    }
    

    代码分析:

    • if条件判断中absent代表虽然在子组件中定义了props,但是父组件并没有传递任何值,然后&条件又判断了子组件props有没有提供default默认值选项,如果没有,那么它的值只能为false
    // 父组件未传递fixed
    export default {
      name: 'ParentComponent'
      template: `<child-component />`
    }
    
    // 子组件fixed值取false
    export default {
      name: 'ChildComponent',
      props: {
        fixed: Boolean
      }
    }
    
    • else if条件判断中,判断了两种特殊的props传递方式:
    // Parent Component A
    export default {
      name: 'ParentComponentA',
      template: `<child-component fixed />`
    }
    
    // Parent Component B
    export default {
      name: 'ParentComponentB',
      template: `<child-component fixed="fixed" />`
    }
    

    对于第一个种情况stringIndex-1booleanIndex0,因此value的值为true。对于第二种情况,则需要根据props的定义具体区分:

    // Child Component A
    export default {
      name: 'ChildComponentA'
      props: {
        fixed: [Boolean, String]
      }
    }
    
    // Child Component B
    export default {
      name: 'ChildComponentB',
      props: [String, Boolean]
    }
    
    1. 对于ChildComponentA来说,由于stringIndex值为1booleanIndex值为0booleanIndex < stringIndex因此可以认为Boolean具有更高的优先级,此时value的值为true
    2. 对于ChildComponentB来说,由于stringIndex值为0booleanIndex值为1stringIndex < booleanIndex因此可以认为String具有更高的优先级,此时value的值不处理。

    处理default默认数据

    处理完Boolean类型后,来处理默认值,既提到过的虽然子组件定义了props,但父组件没有传递的情况。

    // 父组件未传递fixed
    export default {
      name: 'ParentComponent'
      template: `<child-component />`
    }
    
    // 子组件提供了default选项
    export default {
      name: 'ChildComponent',
      props: {
        fixed: {
          type: Boolean,
          default: false
        }
      }
    }
    

    对于以上案例会走如下代码的逻辑:

    if (value === undefined) {
      value = getPropDefaultValue(vm, prop, key)
    }
    
    function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
      // no default, return undefined
      if (!hasOwn(prop, 'default')) {
        return undefined
      }
      const def = prop.default
      // warn against non-factory defaults for Object & Array
      if (process.env.NODE_ENV !== 'production' && isObject(def)) {
        warn(
          'Invalid default value for prop "' + key + '": ' +
          'Props with type Object/Array must use a factory function ' +
          'to return the default value.',
          vm
        )
      }
      // the raw prop value was also undefined from previous render,
      // return previous default value to avoid unnecessary watcher trigger
      if (vm && vm.$options.propsData &&
        vm.$options.propsData[key] === undefined &&
        vm._props[key] !== undefined
      ) {
        return vm._props[key]
      }
      // call factory function for non-Function types
      // a value is Function if its prototype is function even across different execution context
      return typeof def === 'function' && getType(prop.type) !== 'Function'
        ? def.call(vm)
        : def
    }
    

    代码分析:

    1. 首先判断了子组件有没有提供default默认值选项,没有则直接返回undefined
    2. 随后判断了default如果是引用类型,则提示必须把default写成一个函数,既:
    default: {}
    default: []
    
    // 必须写成
    default () {
      return {}
    }
    default () {
      return []
    }
    
    1. 最后再根据default的类型来取值,如果是函数类型则调用这个函数,如果不是函数类型则直接使用。
    2. 其中下面一段代码在这里并不会说明和分析它的具体作用,而是会在props更新章节来介绍。
    if (vm && vm.$options.propsData &&
      vm.$options.propsData[key] === undefined &&
      vm._props[key] !== undefined
    ) {
      return vm._props[key]
    }
    

    props断言

    最后来分析一下props断言。

    function assertProp (
      prop: PropOptions,
      name: string,
      value: any,
      vm: ?Component,
      absent: boolean
    ) {
      if (prop.required && absent) {
        warn(
          'Missing required prop: "' + name + '"',
          vm
        )
        return
      }
      if (value == null && !prop.required) {
        return
      }
      let type = prop.type
      let valid = !type || type === true
      const expectedTypes = []
      if (type) {
        if (!Array.isArray(type)) {
          type = [type]
        }
        for (let i = 0; i < type.length && !valid; i++) {
          const assertedType = assertType(value, type[i])
          expectedTypes.push(assertedType.expectedType || '')
          valid = assertedType.valid
        }
      }
    
      if (!valid) {
        warn(
          getInvalidTypeMessage(name, value, expectedTypes),
          vm
        )
        return
      }
      const validator = prop.validator
      if (validator) {
        if (!validator(value)) {
          warn(
            'Invalid prop: custom validator check failed for prop "' + name + '".',
            vm
          )
        }
      }
    }
    

    assertProp中有三种情况需要去断言:

    • required:如果子组件props提供了required选项,代表这个props必须在父组件中传递值,如果不传递则抛出错误信息Missing required prop: fixed
    • 对于定义了多个type的类型数组,则会遍历这个类型数组,只要当前props的类型和类型数组中某一个元素匹配则终止遍历。,否则抛出错误提示信息。
    // Parent Component
    export default {
      name: 'ParentComponent',
      template: `<child-component :age="true" />`
    }
    // Chil Component
    export default {
      name: 'ChilComponent',
      props: {
        age: [Number, String]
      }
    }
    
    // 报错:Invalid prop: type check failed for prop age,Expected Number, String,got with value true
    
    • 用户自己提供的validator校验器也需要进行断言:
    // Parent Component
    export default {
      name: 'ParentComponent',
      template: `<child-component :age="101" />`
    }
    // Chil Component
    export default {
      name: 'ChilComponent',
      props: {
        age: {
          type: Number,
          validator (value) {
            return value >=0 && value <=100
          }
        }
      }
    }
    
    // 报错:Invalid prop: custom validator check failed for prop age
    

    props更新

    都知道子组件的props值来源于父组件,当父组件值更新时,子组件的值也会发生改变,同时触发子组件的重新渲染。先跳过父组件的具体编译逻辑,直接看父组件的值更新,改变子组件props值的步骤:

    export function updateChildComponent (
      vm: Component,
      propsData: ?Object,
      listeners: ?Object,
      parentVnode: MountedComponentVNode,
      renderChildren: ?Array<VNode>
    ) {
      // 省略代码
      // update props
      if (propsData && vm.$options.props) {
        toggleObserving(false)
        const props = vm._props
        const propKeys = vm.$options._propKeys || []
        for (let i = 0; i < propKeys.length; i++) {
          const key = propKeys[i]
          const propOptions: any = vm.$options.props // wtf flow?
          props[key] = validateProp(key, propOptions, propsData, vm)
        }
        toggleObserving(true)
        // keep a copy of raw propsData
        vm.$options.propsData = propsData
      }
    }
    

    代码分析:

    1. 以上vm实例为子组件,propsData为父组件中传递的props的值,而_propKeys是之前props初始化过程中缓存起来的所有的props的key。
    2. 在父组件值更新后,会通过遍历propsKey来重新对子组件props进行校验求值,最后赋值。

    以上代码就是子组件props更新的过程,在props更新后会进行子组件的重新渲染,这个重新渲染的过程分两种情况:

    • 普通props值被修改:当props值被修改后,其中有段代码props[key] = validateProp(key, propOptions, propsData, vm)根据响应式原理,会触发属性的setter,进而子组件可以重新渲染。
    • 对象props内部属性变化:当这种情况发生时,并没有触发子组件prop的更新,但是在子组件渲染的时候读取到了props,因此会收集到这个propsrender watcher,当对象props内部属性变化的时候,根据响应式原理依然会触发setter,进而子组件可以重新进行渲染。

    toggleObserving作用

    toggleObserving是定义在src/core/observer/index.js文件中的一个函数,其代码很简单:

    export let shouldObserve: boolean = true
    export function toggleObserving (value: boolean) {
      shouldObserve = value
    }
    

    它的作用就是修改当前模块的shouldObserve变量,用来控制在observe的过程中是否需要把当前值变成一个observer对象。

    export function observe (value: any, asRootData: ?boolean): Observer | void {
      if (!isObject(value) || value instanceof VNode) {
        return
      }
      let ob: Observer | void
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__
      } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
        ob = new Observer(value)
      }
      if (asRootData && ob) {
        ob.vmCount++
      }
      return ob
    }
    

    接下来来分析,在处理props的过程中,什么时候toggleObserving(true),什么时候toggleObserving(false)以及为什么需要这样处理?

    function initProps (vm: Component, propsOptions: Object) {
      if (!isRoot) {
        toggleObserving(false)
      }
      // 省略defineReactive的过程
      toggleObserving(true)
    }
    

    props初始化的时候: 可以看到在最开始判断了当为非根实例(子组件)的时候,进行了toggleObserving(false)的操作,这样做的目的是因为:当非根实例的时候,组件的props来自于父组件。当props为对象或者数组时,根据响应式原理,会递归遍历子属性然后进行observe(val),而正是因为props来源于父组件,这个过程其实已经在父组件执行过了,如果不做任何限制,那么会在子组件中又重复一次这样的过程,因此这里需要toggleObserving(false),用来避免递归props子属性的情况,这属于响应式优化的一种手段。在代码最后,又调用了toggleObserving(true),把shouldObserve的值还原。

    props校验的时候: 先来看props提供了default默认值,且默认值返回了对象或者数组。

    export default {
      props: {
        point: {
          type: Object,
          default () {
            return {
              x: 0,
              y: 0
            }
          }
        },
        list: {
          type: Array,
          default () {
            return []
          }
        }
      }
    }
    

    对于以上pointlist取默认值的情况,这个时候的props值与父组件没有关系,那么这个时候需要toggleObserving(true),在observe后再把shouldObserve变量设置为原来的值。

    export function validateProp () {
      // 省略代码
      if (value === undefined) {
        value = getPropDefaultValue(vm, prop, key)
        const prevShouldObserve = shouldObserve 
        toggleObserving(true)
        observe(value)
        toggleObserving(prevShouldObserve)
      }
    }
    

    props更新的时候: 当父组件更新的时候,会调用updateChildComponent()方法,用来更新子组件的props值,这个时候其实和props初始化的逻辑一样,同样不需要对指向父组件的对象或数组props进行递归子属性observe的过程,因此这里需要执行toggleObserving(false)

    export function updateChildComponent () {
      // update props
      if (propsData && vm.$options.props) {
        toggleObserving(false)
        const props = vm._props
        const propKeys = vm.$options._propKeys || []
        for (let i = 0; i < propKeys.length; i++) {
          const key = propKeys[i]
          const propOptions: any = vm.$options.props // wtf flow?
          props[key] = validateProp(key, propOptions, propsData, vm)
        }
        toggleObserving(true)
        vm.$options.propsData = propsData
      }
    }
    

    整体流程图

    在分析完以上所有与props相关的逻辑后,可以总结如下流程图。

    Vue源码解读(响应式原理介绍和Prop)


    起源地下载网 » Vue源码解读(响应式原理介绍和Prop)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元