美文网首页
vue2-生命周期2

vue2-生命周期2

作者: AAA前端 | 来源:发表于2021-05-31 22:08 被阅读0次

    初始化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

          本文链接:https://www.haomeiwen.com/subject/uxxisltx.html