vue2-生命周期2

初始化inject

export function initInjections (vm: Component) {
  // resolveInject 通过用户配置的inject,自底向上搜索可用的注入内容,把搜索结果返回
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    //  设置 接下来shouldObserve 为false 不会设置响应式 
    toggleObserving(false)
    // 把 每一项都 设置 到 vue实例上
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    // 恢复可以设置响应式
    toggleObserving(true)
  }
}

resolveInject 的作用是 通过用户配置的inject,自底向上搜索可用的注入内容,把搜索结果返回。

在循环注入前设置了
toggleObserving(false)循环结束之后,设置toggleObserving(true)`
其作用是通知 defineReactive 函数不要将 内容转换为响应式。 其原理就是将值转换为响应式之前,判断 observerState.shouldConvert属性即可。

即 能通过访问 vm[key] 得到值,但是 不能是响应的,不然在当前实例 修改 vm[key] 会修改 provide 提供 的值
result[key] 即 provide提供的值 是响应式的,这样再 provide中 值变化的时候, 在当前实例 可以得到 更新。

resolveInject 的原理是 如何 自底向上搜索可用的注入内容的呢?

主要思想是: 读出 用户在当前组件中设置的inject的key, 然后循环key, 将每一个 key 从当前组件起, 不断向父组件查找是否有值,找到了就停止循环,最终将所有的key对应的值一起方法即可。

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    // 如果支持 symbol, 用 Reflect.ownkeys读取 symbol类型的属性,否则用Object.keys读取
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    // 比那里 indect 列表
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // #6574 in case the inject object is observed...
      // inject 已经在 vue的实例上 跳过
      if (key === '__ob__') continue
      // 从 from 属性 获取 对应的 provide源属性key  。 injdect里面的属性都会处理为injdect:{ key: {from: 'test'}}
      const provideKey = inject[key].from
      let source = vm
      // 循环 向上 查找  ,实例上的 _provided中存在 inject获取的 key时,赋值给result对象
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }
        source = source.$parent
      }
      // 如果向上遍历所有的实例 都没有 找到,此时 source 不存在
      // 尝试获取默认值 ,默认值如果是函数执行后返回 ,否则直接返回
      // 如果没有默认值 ,开发环境警告
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    // 返回 获取 到 的inject对应的值对象
    return result
  }
}

其中用户设置 的inject 比如

{inject: ['foo']}

规格化之后是下面这样

{inject: { foo: { from: 'foo'}}}

无论是数值形式还是 对象中使用 from属性的形式,本质上其实是让用户设置原属性名与当前组件中的属性名。 如果用户设置的是数组, 那么就认为用户是让两个属性名保持一致。

初始化 状态

在使用vue开发中, 经常会用到一下状态,比如 props, methods, data, computed, watch. 这些状态在使用之前需要进行初始化。

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 按顺序初始化状态 props, methods, data, computed, watch
  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)
  }
}

首先会在 vm上新增一个属性_watchers,用来保持当前组件中所有的watcher实例。 无论是使用 vm.$watch注册的watcher实例 还是使用watch选项添加的watcher实例,都会添加到vm._watchers中。

接下来就是 按顺序判断 是否有props属性,存在就调用initProps初始化props. 是否存在methods。。。。

其中如果data不存在,那么会使用observe观察空对象。

初始化props
props原理是: 父组件提供数据, 子组件通过props字段选择自己需要的内容, Vue.js内部通过 子组件的props选项将需要的数据筛选出来之后添加到子组件的上下文中。

  1. props格式化
    子组件在被实例化时,会对props进行格式化处理。
// 格式化props 每一项都有type,并且 key为驼峰写法
function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  // 如果没有props 直接返回
  if (!props) return
  // 保存props 的临时对象 res
  const res = {}
  let i, val, name
  // props 是数组
  if (Array.isArray(props)) {
    i = props.length
    // 遍历
    while (i--) {
      val = props[i]
      // 如果 数组中 每一项是字符串, 保存对应的props。
      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)) {
    // props是对象 ,遍历
    for (const key in props) {
      val = props[key]
// 将名称 驼峰化  比如 user-name  ==>  userName
      name = camelize(key)
      // 如果每一项 对应的值 是 对象直接赋值 ,否则 包装一层
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    // props 不是对象又不是 数组 ,开发环境警告
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  // 把格式化后的 props重新赋值 给props属性
  options.props = res
}
  1. 初始化props
    初始化 props内部原理是: 通过规格化之后的 props从 父组件传入的props数据中 或从使用 new 创建实例 时传入的propsData参数中,筛选出 需要的数据 保存在 vm._props中,然后在vm上设置一个代理, 实现通过 vm.x访问 vm._props.x的目的。
function initProps (vm: Component, propsOptions: Object) {
  // vm.$options.propsData 是用户通过父组件传入或用户 new Vue 时 传入的 props
  const propsData = vm.$options.propsData || {}
  // _props 中 会保存 所有设置到 props变量中的属性
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 缓存道具键,以便将来更新可以使用数组进行迭代
  // 缓存props对象中 的key 
  const keys = vm.$options._propKeys = []
  // 当前实例 是不是 根实例
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    // 只有root实例的props属性应该被转换为响应式  不能被 observe观察 即不能 new Observe
    toggleObserving(false)
  }
  // 循环propsOptions ,将key添加到keys中。 调用validateProp 函数得到 prop的值,通过
  // defineReactive 添加到 vm._props中 变为响应式
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    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 {
      // toggleObserving(false) 是为了 能通过 props访问 props[key] 对应的value值
      // 但是 不转换为 响应式 (访问和修改 都会 引起 watcher 更新
      // 在 validateProp 中 value 已经是响应式的了。通过父组件修改 值,响应变化
      // 而不能 在当前组件通过 访问 props[key] 修改 value值
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    // 最后判断 key是否再实例 vm中,如果不存在调用 proxy,在 vm上设置一个以key为属性的代理
    // 这样this.[key] 就可以访问 this._props.[key]了
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  // 重置 可响应式 
  toggleObserving(true)
}

toggleObserving 函数 的作用是确定 并 控制 defineReactive函数 调用时所传入 的value参数 是否需要转换为响应式。 toggleObserving 是一个闭包函数,所以能通过调用它并 传入一个参数 来控制 observer/index.js文件的作用域 中的变量 shouldObserve。 这样当数据将要被转换Wie响应式数据时,通过变量 shouldObserve来判断时候需要将数据转换为响应式的。

toggleObserving(false) 是为了 能通过 props访问 props[key] 对应的value值
但是 不转换为 响应式 (访问和修改 都会 引起 watcher 更新
在 validateProp 中 value 已经是响应式的了。通过父组件修改 值,响应变化
而不能 在当前组件通过 访问 props[key] 修改 value值

最后判断 key 在 vm中 是否存在, 如果不存在, 则调用 proxy,在vm上设置一个以 key 为属性的代理,当使用vm[key]访问数据时, 其实访问的是vm._props[key]。

重点是validateProp 函数是如何 获取props内容的

export function validateProp (
  key: string, // 属性名
  propOptions: Object, // 子组件中用户设置的props 选项
  propsData: Object, // 父组件或用户提供 的props 数据
  vm?: Component // this别名 实例
): any {
  const prop = propOptions[key] // 保存当前的prop
  const absent = !hasOwn(propsData, key) // 当前的props属性 缺席 ,不存在
  let value = propsData[key] //获取 prop具体的值
  // boolean casting
  // 处理布尔类型
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  if (booleanIndex > -1) {
    // 如果 prop不存在 并没有默认值 ,那么为 false
    if (absent && !hasOwn(prop, 'default')) {
      value = false
    } else if (value === '' || value === hyphenate(key)) {
      // hyphenate aB驼峰转换回去a-b (normalizeProps初始化时会把key转为驼峰)
      // key 存在 ,当 value为空字符 或 value与key相等 (a==a, userName=user-name)
      // 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
  // props 的值 为undefined的情况下
  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)
    // 把 value默认值 转换为 响应式
    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函数接受4个参数

  • key: propsOptions中属性名
  • propOPtions: 子组件用户设置的props选项
  • propsData: 父组件或用户提供的props数据
  • vm: vue实例上下文

首先解决布尔类型prop的特殊情况

如果key不存在, 父组件或用户没有提供这个数据,并且props选项中没有默认值, 这个时候将value设置为 false. 另一种情况,key存在,当value是空字符或value与key相等,将value设置为true

除了布尔值外,其他类型的prop之需要处理一种情况。 如果子组件通过props选项设置的key 在props数据中不存在时, props如果提供了默认值,就是用它,将默认值转换为响应式。

toggleObserving 可以决定observer调用时,是否会将 value转换为响应式的。 最后 toggleObserving(prevShouldObserve) 将状态恢复成最初的状态。

assertProp 在开发环境下,会 断言prop是否有效。

function assertProp (
  prop: PropOptions, // props选项
  name: string, // props中prop选项的key
  value: any, // prop数据 (propData)
  vm: ?Component, // 上下文
  absent: boolean // prop数据中不存在 key属性
) {
  // 如果 设置了必填项 并且 没有 key 属性 ,控制台 警告 
  if (prop.required && absent) {
    warn(
      'Missing required prop: "' + name + '"',
      vm
    )
    return
  }
  //  如果value 不存在 并且 没有设置 required  是合法的情况,直接返回 undefined即可
  //  null == undefined 为 true
  if (value == null && !prop.required) {
    return
  }
  let type = prop.type
  // valid 默认 为 false , 或者 设置type 为 true时默认为true
  let valid = !type || type === true
  // 保存 type的列表 ,当校验失败,在控制台打印警告时, 可以将变量 expectedTypes中保存的类型打印出来
  const expectedTypes = []
  if (type) {
    // 把type 转换 数组
    if (!Array.isArray(type)) {
      type = [type]
    }
    // 
    for (let i = 0; i < type.length && !valid; i++) {
      // assertType 检验value 。返回一个对象{valid: true, expectedType: "Boolean"}
      // valid表示 是否校验成功  expectedType 表示类型
      const assertedType = assertType(value, type[i], vm)
      expectedTypes.push(assertedType.expectedType || '')
      valid = assertedType.valid
    }
  }
  
  // 循环结束后, haveExpectedTypes 不存在,那么校验失败 警告
  const haveExpectedTypes = expectedTypes.some(t => t)
  if (!valid && haveExpectedTypes) {
    warn(
      getInvalidTypeMessage(name, value, expectedTypes),
      vm
    )
    return
  }
  // 获取 自定义验证函数 
  // 如果设置了 就执行。猴子 调用warn 打印警告
  const validator = prop.validator
  if (validator) {
    if (!validator(value)) {
      warn(
        'Invalid prop: custom validator check failed for prop "' + name + '".',
        vm
      )
    }
  }
}

你可能感兴趣的:(vue2-生命周期2)