vue2学习笔记-数据驱动

251 阅读2分钟

前言

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

<div id="app">
  {{ message }}
</div>


var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

流程图

sparrow.png

步骤

1 初始化

位置:

vue/src/platforms/web/entry-runtime-with-compiler.js
vue/src/platforms/web/runtime/index.js
vue/src/core/index.js
vue/src/core/instance/init.js

初始化主要是为后续的流程提供支撑,包括以下几块的内容

  1. 参数处理,比如 mixin, extend,props
  2. 执行生命周期,比如 beforeCreate、created
  3. 生成内部的一些属性和方法,便于后续操作

这里涉及的内容较多,在后面用到的地方再提及,初始化完成之后就会进行挂载

// vue/src/core/instance/init.js
if (vm.$options.el) {
  vm.$mount(vm.$options.el);
}

2 编译

位置: vue/src/platforms/web/entry-runtime-with-compiler.js 主要流程:

// 1. 保存真正的$mount方法
const mount = Vue.prototype.$mount;
// 2. 编译生成render staticRenderFns 方法,挂载到vm上
const { render, staticRenderFns } = compileToFunctions(...);
options.render = render;
options.staticRenderFns = staticRenderFns;
// 3. 调用真正的$mount方法,开始挂载
return mount.call(this, el, hydrating);

如果是运行时的版本是不需要这一步的,编译的最终目的就是生成render、staticRenderFns,支持$mount过程

3 挂载

$mount 定义在vue/src/platforms/web/runtime/index.js,最终调用的是mountComponent mountComponent位置vue/src/core/instance/lifecycle.js

// 1. 执行beforeMount钩子
callHook(vm, "beforeMount");
// 2.定义方法
updateComponent = () => {
  vm._update(vm._render(), hydrating);
};

// 3. 初始化渲染Watcher
new Watcher(
  vm,
  updateComponent,
  noop,
  {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, "beforeUpdate");
      }
    },
  },
  true /* isRenderWatcher */
);
//4. 执行mounted钩子
if (vm.$vnode == null) {
  vm._isMounted = true;
  callHook(vm, "mounted");
}

可以看出主要是在 new Watcher 的过程中完成了挂载,再看 Watcher 位置: vue/src/core/observer/watcher.js

export default class Watcher{

  constructor() {
    ...
    // 这里的expOrFn就是上面传的updateComponent
    this.getter = expOrFn;
    ...
    this.value = this.lazy ? undefined : this.get();
  }

  get() {
    ...
    try {
      value = this.getter.call(vm, vm);
    }
  }
}

可以看出 new Watcher()中由于 this.lazy 是 undefined,会执行updateComponent,也就是说完成全部挂载任务的就是updateComponent

4 vm._render

位置:vue/src/core/instance/render.js

let vnode;
try {
  vnode = render.call(vm._renderProxy, vm.$createElement);
}
return vnode;

可以看出 render()的主要作用是生成 vnode 并返回,主要执行的是步骤 2 中生成的render,从这里我们可以知道 render 会调用vm.$createElement

位置:vue/src/core/vdom/create-element.js createElement 会根据参数的不同进行不同的处理,最终都是为了生成 vnode 并返回

vnode = new VNode(
  config.parsePlatformTagName(tag),
  data,
  children,
  undefined,
  undefined,
  context
);
return vnode

5vm._update

位置vue/src/core/instance/lifecycle.js

 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);

_update执行__patch__ 位置:vue/src/core/vdom/patch.js patch 的关键流程是执行 createElm:

 const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
createElm(
  vnode,
  insertedVnodeQueue,
  oldElm._leaveCb ? null : parentElm,
  nodeOps.nextSibling(oldElm)
);
...
// 删除旧节点
if (isDef(parentElm)) {
  removeVnodes([oldVnode], 0, 0);
}

createElm 的主要流程如下:

// 1. 生成div
vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);

// 2. 执行invokeCreateHooks ,把vnode里的各种数据转化为html元素的各种属性
invokeCreateHooks(vnode, insertedVnodeQueue);
function invokeCreateHooks(vnode, insertedVnodeQueue) {
    for (let i = 0; i < cbs.create.length; ++i) {
      cbs.create[i](emptyNode, vnode);
    }
    i = vnode.data.hook; // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) i.create(emptyNode, vnode);
      if (isDef(i.insert)) insertedVnodeQueue.push(vnode);
    }
  }

// 3. 插入到页面上
insert(parentElm, vnode.elm, refElm);

createElm 执行完成之后,也就可以看到一个正常的页面了

文章地址

mygithub