Vue源码05-mount

我们在各种初始化都完成的情况下开始Vue挂载

  if (vm.$options.el) {
      vm.$mount(vm.$options.el)  // 调用的是web/entry-runtime-with-compiler.js
    }

mount函数

// runtime/index.js 运行时的$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
// entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  const options = this.$options
  // 转化template/el 为render函数
  if (!options.render) {
    let template = options.template
    if (template) {  // 解析template
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {  // template='#id'
          template = idToTemplate(template) // idToTemplate去查看源代码
        }
      } else if (template.nodeType) {
        template = template.innerHTML 
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el) // 通过el获取template
    }
    if (template) {
      // 对template-->render
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)  // compileToFunctions去源码查看
      options.render = render // // 生成的render函数with(this){return _c("div") }
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating) // 运行是的mount
}

模版编译createCompiler

export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  // 解析ast
  const ast = parse(template.trim(), options)

  if (options.optimize !== false) {
    optimize(ast, options)
  }

  const code = generate(ast, options)

  return {
    ast,
    render: code.render, // with(this){return _c("div") }
    staticRenderFns: code.staticRenderFns
  }
})
  • parse函数是解析出ast,里面就是正则匹配解析template
  • generate根据ast生成render
    这里介绍一下ast
declare type ASTElement = {
  type: 1;
  tag: string;
  attrsList: Array;
  attrsMap: { [key: string]: any };
  rawAttrsMap: { [key: string]: ASTAttr };
  parent: ASTElement | void;
  children: Array;

  start?: number;
  end?: number;

  processed?: true;

  static?: boolean;
  staticRoot?: boolean;
  staticInFor?: boolean;
  staticProcessed?: boolean;
  hasBindings?: boolean;

  text?: string;
  attrs?: Array;
  dynamicAttrs?: Array;
  props?: Array;
  plain?: boolean;
  pre?: true;
  ns?: string;

  component?: string;
  inlineTemplate?: true;
  transitionMode?: string | null;
  slotName?: ?string;
  slotTarget?: ?string;
  slotTargetDynamic?: boolean;
  slotScope?: ?string;
  scopedSlots?: { [name: string]: ASTElement };

  ref?: string;
  refInFor?: boolean;

  if?: string;
  ifProcessed?: boolean;
  elseif?: string;
  else?: true;
  ifConditions?: ASTIfConditions;

  for?: string;
  forProcessed?: boolean;
  key?: string;
  alias?: string;
  iterator1?: string;
  iterator2?: string;

  staticClass?: string;
  classBinding?: string;
  staticStyle?: string;
  styleBinding?: string;
  events?: ASTElementHandlers;
  nativeEvents?: ASTElementHandlers;

  transition?: string | true;
  transitionOnAppear?: boolean;

  model?: {
    value: string;
    callback: string;
    expression: string;
  };

  directives?: Array;

  forbidden?: true;
  once?: true;
  onceProcessed?: boolean;
  wrapData?: (code: string) => string;
  wrapListeners?: (code: string) => string;
};

mountComponent

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
         vm.$options.render = createEmptyVNode
         ...
  }
 // 必须保证render函数存在
  callHook(vm, 'beforeMount') // 这里调用beforeMount钩子

  let updateComponent
  ...
    updateComponent = () => {
      vm._update(vm._render(), hydrating)  // 先调用_render,在调用_update
    }

  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)  // RenderWatcher
  hydrating = false


  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted') // 调用mounted
  }
  return vm
}

关于挂载组件最好是自己去单步调试,查看一下细节,这里做一下简单介绍

  • 先调用_render函数拿到vnode
  • 在调用_update函数
  • 在调用 patch函数
  • 先 createElm(vnode, insertedVnodeQueue)
  • 最后在 removeVnodes([oldVnode], 0, 0)

你可能感兴趣的:(Vue源码05-mount)