vue组件化机制

163 阅读3分钟

组件化机制

1、vue.component()

==任何的全局api 都在global-api目录下==

目录位置:vue\src\core\global-api\assets.js

1、初始化调用 initAssetRegisters()

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        // 这里用正则判断组件的命名
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        // 定义组件化方法的处理
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

以上方法做的事情是初始化时给Vue挂上components和directive方法

具体分析


//  Vue.components('my-com', {template: '<div>123</div>'})

    if (type === 'component' && isPlainObject(definition)) {
        definition.name = definition.name || id
        definition = this.options._base.extend(definition)
    }

很迷惑 this.options._base 是什么

在global-api的index.js 有这样的一行代码,所以

Vue.options._base = Vue
// this.options._base.extend(definition)  等同于 Vue.extend(definition)

2、针对Vue.extends()研究

目录位置:src\core\global-api\extend.js

当前主要是return了一个组件的构造函数

Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) 
    // 如果传入已存在构造函数
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // 校验组件名
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
    
    
    // 和原本的vue一样的初始化方法
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 同样的原型
    Sub.prototype = Object.create(Super.prototype)
    
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    //组件自调用注册自己
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

3、何时调用

我们先来看看render执行的函数

==render函数是 通过template解析会生成以下字符数再通过new Function('这段字符串')生成函数==

with(this){return _c('div',{attrs:{"id":"demo"}},[
 _c('h1',[_v("虚拟DOM")]),_v(" "),
 _c('p',[_v(_s(foo))]),_v(" "),
 _c('comp') // 对于组件的处理并⽆特殊之处
],1)}

解惑: _c 是什么

在 初始化的时候 执行 initRender()的时候

  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

目前的已知的执行顺序是

new Vue() => $mount() => vm._render() => createElement() => _createElement(context, tag, data, children, normalizationType)

4、解析_createElement函数

目录文件 src\core\vdom\create-element.js

标准化处理

// 生成vnode

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // 有data被响应返回空
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  
  // object syntax in v-bind  // 处理is属性
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  
  //tag不存在
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  

  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  
  // children的标准化处理
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  // 以上都是标准化处理

  let vnode, ns
  
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) 
    // 命名空间
    // 常规标签
    // 如果是普通标签
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      // 直接生成vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      
      
      // resolveAsset 动态加载
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // 获取构造函数
      // 创建组件
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 或者用户直接传递组件的配置对象或者构造函数
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  
  
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}

createElement 针对普通标签的创建、针对组件、针对动态引入的组件创建

5、针对createComponent(Ctor, data, context, children, tag)研究

该方法返回一个vnode

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor // 如果传的是配置
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor) // 处理成函数
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // async component 异步组件会生成临时占位符
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // 异步组件这里就返回占位符了

  data = data || {}

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor) // 将options合并

  // transform component v-model data into props & events // 处理v-model
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // extract props 属性和特性分开
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // functional component 函数式组件怎么做
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on // 自定义事件
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn // 原生事件

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // install component management hooks onto the placeholder node 
  // 安装自定义钩子 (超重要)
  installComponentHooks(data)

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  // //  tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string, elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

  return vnode
}


这里 createComponent 返回的只是vnode。得到虚拟dom

6、针对 installComponentHooks(data)

存在四个钩子,区别去生命周期的钩子
const componentVNodeHooks = {
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    // ...
  },

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // ...
  },

  insert (vnode: MountedComponentVNode) {
    // ...
  },

  destroy (vnode: MountedComponentVNode) {
    // ...
  }
}
对于init钩子什么时候被调用

root.$mount() => patch => createElm() => createComponent() => init()

==执行以下函数才会真正的实例化==

  function init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch  keptalive的处理
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 真正的实例化
      // vnode.componentInstance 虚拟dom保存组件实例
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // 执行挂载
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

备注: createComponentInstanceForVnode 解析一下

function createComponentInstanceForVnode (
  vnode: any,
  // activeInstance in lifecycle state
  parent: any
) {
    return new vnode.componentOptions.Ctor(options)
}

在patch过程中遇到组件标签创建组件的时候 createComponent调用 init 会获取组件实例

通过 new vnode.componentOptions.Ctor(options) 会生成vueComponent对象但dom未挂载

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) { // 看这里!!!!!!!
        i(vnode, false /* hydrating */)
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }