组件化 组件化,把页面拆分成若干个组件,每个组件依赖的 css,js,模板,图片等资源放在一块,组件之间是相互独立的,可已复用与嵌套 组件 vnode 的创建什么时候执行 在 vm._update 中会调用_createComponent 方法,这个方法有一段代码是先判断 tag 是否是一个普通 div,如果是普通 div,那么会实例化一个普通的 VNode,否则就通过 createCompone…

组件化

组件化,把页面拆分成若干个组件,每个组件依赖的 css,js,模板,图片等资源放在一块,组件之间是相互独立的,可已复用与嵌套

import Vue from 'vue';
import App from './App.vue';

var app = new Vue({
  el: '#app',
  // 这里的 h 是 createElement 方法
  render: (h) => h(App)
});

组件 vnode 的创建什么时候执行

在 vm._update 中会调用_createComponent 方法,这个方法有一段代码是先判断 tag 是否是一个普通 div,如果是普通 div,那么会实例化一个普通的 VNode,否则就通过 createComponent 去创建一个组件 VNode。

// \src\core\vdom\create-element.js
if (typeof tag === 'string') {
  let Ctor;
  ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
  if (config.isReservedTag(tag)) {
    // platform built-in elements
    vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context);
  } else if (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 {
  // 组件vnode创建的过程
  vnode = createComponent(tag, data, context, children);
}
// createComponent的过程例子
import HelloWorld from './components/HelloWorld';

export default {
  name: 'app',
  components: {
    HelloWorld
  }
};

组件vnode创建的过程核心步骤

  1. 先进行 Vue 构造函数的 options 和用户传入 options 进行一次合并到 vm.$options 上
  2. 再进行构造子类构造函数:返回了一个继承与 Vue 构造器,并初始化了 props 等属性的 Sub 实例,有跟 Vue 样的能力
  3. 安装组件构造函数:把进行 patch 时产生的钩子函数 merge 到 data.hook 中
  4. 生成一个 VNode,没有 children,多了 componentOptions
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;
  }
  // options合并后的结果
  const baseCtor = context.$options._base;

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    // 在\src\core\global-api\index.js中有一段代码 Vue.options._base = Vue
    // 在src/core/instance/init.j中有一段代码
    //  vm.$options = mergeOptions(
    //     resolveConstructorOptions(vm.constructor),
    //     options || {},
    //     vm
    //   )
    // 这样就把Vue的option扩展到vm.$option上了,可以通过vm.$options._base访问到Vue这个构造函数了。
    // 在上例中baseCtor相当于Vue 相当于Vue.extend
    // 核心步骤1:构造子类构造函数
    Ctor = baseCtor.extend(Ctor);
  }

  // 创建一个构造器的时候不是一个函数的时候,报错
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context);
    }
    return;
  }

  // 一些异步组件
  let asyncFactory;
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor;
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);
    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 = data || {};

  // resolve constructor options in case global mixins are applied after
  // 对option处理,因为有可能Sub的属性可能北全局的mixin影响
  resolveConstructorOptions(Ctor);

  // 对v-model对的处理
  if (isDef(data.model)) {
    transformModel(Ctor.options, data);
  }

  // 对props进行处理,生成prop data
  const propsData = extractPropsFromVNodeData(data, Ctor, tag);

  // 对函数组件的处理
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children);
  }

  // 对自定义事件的处理
  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;
    }
  }

  // 核心步骤2:安装组件钩子函数
  installComponentHooks(data);

  // 核心步骤3:实例化 VNode
  const name = Ctor.options.name || tag;
  // 组件的vnode是没有children的,在patch时会用到为空的情况
  // tag,data,chidlren,text,elm,context,componentOptions
  const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory);

  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode);
  }

  return vnode;
}
  1. 核心步骤 1 构造子类构造函数:用到的 Vue.extend

    Vue.extend 作用就是构造一个 Vue 的子类,使用原型继承,把一个纯对象转换一个继承于 Vue 的构造器 Sub 并返回,扩展了一些属性,并对 props 和 computed 做了初始化的,最后对 Sub 进行缓存,避免多次 extend 对组件重复构造。

// src/core/global-api/extend.js
Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    // this指向Vue 是静态
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // 如果已经有了一个Sub,return出去,不用继续建立构造函数
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    // name就是组件name
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      // 进行校验,以字母开头,连字符结尾,不满足会报错,传入是个内置的标签,也会报错
      validateComponentName(name)
    }
    // 子的构造数
    const Sub = function VueComponent (options) {
      // 又走到了vue初始化的过程,
      this._init(options)
    }
    // 把子构造器的原型加到父构造器原型
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // 自身options和vue的options进行合并
    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)
    }

    // Vue全局的静态方法附给Sub,目的是让Sub和Vue同样的能力
    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.
    // Vue的属性附给Sub
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // 缓存下来,为了只执行一次
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
  1. 核心步骤 2 安装组件钩子函数:用到的 installComponentHooks

    installComponentHooks 的过程就是把 componentVNodeHooks 的钩子函数 merge 到 data.hook 上

function mergeHook(f1: any, f2: any): Function {
  const merged = (a, b) => {
    // 依次执行这两个钩子函数即可
    f1(a, b);
    f2(a, b);
  };
  merged._merged = true;
  return merged;
}
const hooksToMerge = Object.keys(componentVNodeHooks);
function installComponentHooks(data: VNodeData) {
  const hooks = data.hook || (data.hook = {});
  // 组件默认有几个钩子,对钩子进行遍历,把钩子merge到data.hook上
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i];
    const existing = hooks[key];
    // 在 patch 过程中暴露出各种实际的钩子函数
    const toMerge = componentVNodeHooks[key];
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge;
    }
  }
}
  1. 核心步骤 2 安装组件钩子函数:用到的 componentVNodeHooks

    在 patch 过程中暴露出各种实际的钩子函数

const componentVNodeHooks = {
  init(vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
      const child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance));
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    }
  },

  prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions;
    const child = (vnode.componentInstance = oldVnode.componentInstance);
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    );
  },

  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode;
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true;
      callHook(componentInstance, 'mounted');
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance);
      } else {
        activateChildComponent(componentInstance, true /* direct */);
      }
    }
  },

  destroy(vnode: MountedComponentVNode) {
    const { componentInstance } = vnode;
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy();
      } else {
        deactivateChildComponent(componentInstance, true /* direct */);
      }
    }
  }
};

暂无评论