vue2学习笔记-组件化

前言

跟着黄轶大神学习的 vue,做一些简单记录。

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

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

流程图

vue2学习笔记-组件化_第1张图片

步骤

1 $mount 之前

这里的逻辑和数据驱动中的大致逻辑是一样的,都是在做初始化,到了下一步在会有所区别

2 $mount

  1. 位置:vue/src/core/instance/lifecycle.js _update方法中的updateComponent
  2. vm._render()要生成 App 组件的 vnode,vm._update()要将组件 vnode 生成 dom 渲染载页面上,和数据驱动中的略有不同

3 生成 vnode

  1. 位置:vue/src/core/vdom/create-element.js
  2. vm_render()实际执行的就是_createElement()方法,与 不同的是,这里的tag是个组件,所以走 else 逻辑,执行 createComponent
...
if (typeof tag === "string") {
  ...
else {
  vnode = createComponent(tag, data, context, children);
}
...

createComponent 影响关键流程的是以下三部分

3.1

const baseCtor = context.$options._base;
Ctor = baseCtor.extend(Ctor);

这里的baseCtor其实就是指的 Vue,也就是执行了Vue.extend()方法,extend 方法会返回一个 Sub 构造函数,其逻辑和 Vue 构造函数基本一致(某种程度上可以把它当作 Vue,只在细微之处有区别,是继承 Vue 的),所以最后 Ctor 就是 Sub(一个继承 Vue 的构造函数)

// vue/src/core/global-api/extend.js
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub

3.2

这里会往 data.hook 里注入几个 hook,其中的init hook将会发挥重大作用

3.3

生成一个组件 vnode 并返回,这里和普通 vnode 的区别是 vnode 的属性存放的地方不一样,组件 vnode 都放在.componentOptions 上

4 挂载

  1. 位置:vue/src/core/instance/lifecycle.js Vue.prototype._update
  2. 在 3 执行完成后就得到了 vnode,这里vm._update就是需要根据 vnode 生成 html 元素并挂载到页面上,实际执行的就是__patch__方法

5 挂载细节:内部组件初始化、挂载到外层组件上

  1. 位置:vue/src/core/vdom/patch.js patch
  2. 和之前数据驱动一样,走的 oldnode === isRealElement 的逻辑,进入createElm,再进入createComponent
  3. createComponent 共执行两块逻辑
    1. data.hook.init 为 true,所以执行 init(3.2 中放到 data.hook 里的)
    2. vnode.componentInstance 为 true,执行相关逻辑,返回 true,到达外层createElm之后也直接执行 return 结束整个createElm
// fn createComponent
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 */);
  }
  if (isDef(vnode.componentInstance)) {
    initComponent(vnode, insertedVnodeQueue);
    insert(parentElm, vnode.elm, refElm);
    if (isTrue(isReactivated)) {
      reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
    }
    return true;
  }
}

5.1 init 过程

const options: InternalComponentOptions = {
  _isComponent: true,
  _parentVnode: vnode, // 这里就是组件vnode
  parent, // 当前的new Vue的实例vm
};
...
const child = (vnode.componentInstance = createComponentInstanceForVnode(
  vnode,
  activeInstance
));
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
  1. 位置:vue/src/core/vdom/create-component.js init
  2. init 也是执行两步:
    1. 调用 createComponentInstanceForVnode
    2. child.$mount

createComponentInstanceForVnode 主要是return new vnode.componentOptions.Ctor(options),这里的vnode.componentOptions.Ctor就是 3.3 中返回的 Sub 构造函数,所以 createComponentInstanceForVnode 也就是这里的代码可以理解为return new Sub(options),这里也就是相当于初始化 Sub 实例了(但由于 vm. o p t i o n s . e l 是 u n d e f i n e d , 所 以 不 会 走 options.el是undefined,所以不会走 options.elundefinedmount),值得一提的是这里的 option 和数据驱动的不太一样的地方是,多了三个属性_isComponent、_parentVnode、parent,我理解只有组件 vnode 才会有这三个属性,这是为了组件元素和其内部进行连接、通信

child.$mount 和数据驱动过程基本一样,但不同点在于child.$mount 并没有挂载到页面上,而是记录在 vnode.elm 中(insert 的 parent 参数为 undefined)

// fn  createElm => insert
  function insert(parent, elm, ref) {
    if (isDef(parent)) {
      if (isDef(ref)) {
        if (nodeOps.parentNode(ref) === parent) {
          nodeOps.insertBefore(parent, elm, ref);
        }
      } else {
        nodeOps.appendChild(parent, elm);
      }
    }
  }

从以上可以看出 init 其实就是在初始化 Sub 实例

5.2 挂载过程

  initComponent(vnode, insertedVnodeQueue);
  insert(parentElm, vnode.elm, refElm);

这里共执行 2 个函数,initComponent 的关键流程是:

 vnode.elm = vnode.componentInstance.$el;

这里的 vnode.componentInstance.$el 就是前面组件初始化生成的 html 元素

// _update方法
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
// __patch__方法返回的就是vnode.elm

此流程过后组件 vnode.elm 就有了该组件生成的 html 元素,接下来的 insert 就会将这段 html 元素挂载到页面上,createElm 函数整体结束

6 结束

到这里整个渲染过程就结束了

文章地址

mygithub

你可能感兴趣的:(js,vue,web,vue.js,前端)