前端进阶-手写Vue2.0源码(三)|技术点评

前言

今天是个特别的日子 祝各位女神女神节快乐哈 封面我就放一张杀殿的帅照表达我的祝福 哈哈

此篇主要手写 Vue2.0 源码-初始渲染原理

上一篇咱们主要介绍了 Vue 模板编译原理 它是 Vue 生成虚拟 dom 的基础 模板编译最后转化成了 render 函数 之后又如何能生成真实的 dom 节点去替换掉 el 选项配置呢 那么通过此篇的学习就可以知道 Vue 初始渲染的流程 此篇主要包含虚拟 dom 以及真实 dom 的生成

适用人群: 没时间去看官方源码或者看源码看的比较懵而不想去看的同学


正文

1.组件挂载入口

// src/init.js

Vue.prototype.$mount = function (el) {
  const vm = this;
  const options = vm.$options;
  el = document.querySelector(el);

  // 如果不存在render属性
  if (!options.render) {
    // 如果存在template属性
    let template = options.template;

    if (!template && el) {
      // 如果不存在render和template 但是存在el属性 直接将模板赋值到el所在的外层html结构(就是el本身 并不是父元素)
      template = el.outerHTML;
    }

    // 最终需要把tempalte模板转化成render函数
    if (template) {
      const render = compileToFunctions(template);
      options.render = render;
    }
  }

  // 将当前组件实例挂载到真实的el节点上面
  return mountComponent(vm, el);
};
复制代码

接着看$mount 方法 我们主要关注最后一句话 mountComponent 就是组件实例挂载的入口函数 这个方法放在源码的 lifecycle 文件里面 代表了与生命周期相关 因为我们组件初始渲染前后对应有 beforeMount 和 mounted 生命周期钩子

2.组件挂载核心方法 mountComponent

// src/lifecycle.js
export function mountComponent(vm, el) {
  // 上一步模板编译解析生成了render函数
  // 下一步就是执行vm._render()方法 调用生成的render函数 生成虚拟dom
  // 最后使用vm._update()方法把虚拟dom渲染到页面

  // 真实的el选项赋值给实例的$el属性 为之后虚拟dom产生的新的dom替换老的dom做铺垫
  vm.$el = el;
  //   _update和._render方法都是挂载在Vue原型的方法  类似_init
  vm._update(vm._render());
}
复制代码

新建 lifecycle.js 文件 表示生命周期相关功能 核心导出 mountComponent 函数 主要使用 vm._update(vm._render())方法进行实例挂载

3.render 函数转化成虚拟 dom 核心方法 _render

// src/render.js

import { createElement, createTextNode } from "./vdom/index";

export function renderMixin(Vue) {
  Vue.prototype._render = function () {
    const vm = this;
    // 获取模板编译生成的render方法
    const { render } = vm.$options;
    // 生成vnode--虚拟dom
    const vnode = render.call(vm);
    return vnode;
  };

  // render函数里面有_c _v _s方法需要定义
  Vue.prototype._c = function (...args) {
    // 创建虚拟dom元素
    return createElement(...args);
  };

  Vue.prototype._v = function (text) {
    // 创建虚拟dom文本
    return createTextNode(text);
  };
  Vue.prototype._s = function (val) {
    // 如果模板里面的是一个对象  需要JSON.stringify
    return val == null
      ? ""
      : typeof val === "object"
      ? JSON.stringify(val)
      : val;
  };
}
复制代码

主要在原型定义了_render 方法 然后执行了 render 函数 我们知道模板编译出来的 render 函数核心代码主要 return 了 类似于_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))这样的代码 那么我们还需要定义一下_c _v _s 这些函数才能最终转化成为虚拟 dom

// src/vdom/index.js

// 定义Vnode类
export default class Vnode {
  constructor(tag, data, key, children, text) {
    this.tag = tag;
    this.data = data;
    this.key = key;
    this.children = children;
    this.text = text;
  }
}

// 创建元素vnode 等于render函数里面的 h=>h(App)
export function createElement(tag, data = {}, ...children) {
  let key = data.key;
  return new Vnode(tag, data, key, children);
}

// 创建文本vnode
export function createTextNode(text) {
  return new Vnode(undefined, undefined, undefined, undefined, text);
}
复制代码

新建 vdom 文件夹 代表虚拟 dom 相关功能 定义 Vnode 类 以及 createElement 和 createTextNode 方法最后都返回 vnode

4.虚拟 dom 转化成真实 dom 核心方法 _update

// src/lifecycle.js

import { patch } from "./vdom/patch";
export function lifecycleMixin(Vue) {
  // 把_update挂载在Vue的原型
  Vue.prototype._update = function (vnode) {
    const vm = this;
    // patch是渲染vnode为真实dom核心
    patch(vm.$el, vnode);
  };
}
复制代码
// src/vdom/patch.js

// patch用来渲染和更新视图 今天只介绍初次渲染的逻辑
export function patch(oldVnode, vnode) {
  // 判断传入的oldVnode是否是一个真实元素
  // 这里很关键  初次渲染 传入的vm.$el就是咱们传入的el选项  所以是真实dom
  // 如果不是初始渲染而是视图更新的时候  vm.$el就被替换成了更新之前的老的虚拟dom
  const isRealElement = oldVnode.nodeType;
  if (isRealElement) {
    // 这里是初次渲染的逻辑
    const oldElm = oldVnode;
    const parentElm = oldElm.parentNode;
    // 将虚拟dom转化成真实dom节点
    let el = createElm(vnode);
    // 插入到 老的el节点下一个节点的前面 就相当于插入到老的el节点的后面
    // 这里不直接使用父元素appendChild是为了不破坏替换的位置
    parentElm.insertBefore(el, oldElm.nextSibling);
    // 删除老的el节点
    parentElm.removeChild(oldVnode);
    return el;
  }
}
// 虚拟dom转成真实dom 就是调用原生方法生成dom树
function createElm(vnode) {
  let { tag, data, key, children, text } = vnode;
  //   判断虚拟dom 是元素节点还是文本节点
  if (typeof tag === "string") {
    //   虚拟dom的el属性指向真实dom
    vnode.el = document.createElement(tag);
    // 解析虚拟dom属性
    updateProperties(vnode);
    // 如果有子节点就递归插入到父节点里面
    children.forEach((child) => {
      return vnode.el.appendChild(createElm(child));
    });
  } else {
    //   文本节点
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}

// 解析vnode的data属性 映射到真实dom上
function updateProperties(vnode) {
  let newProps = vnode.data || {};
  let el = vnode.el; //真实节点
  for (let key in newProps) {
    // style需要特殊处理下
    if (key === "style") {
      for (let styleName in newProps.style) {
        el.style[styleName] = newProps.style[styleName];
      }
    } else if (key === "class") {
      el.className = newProps.class;
    } else {
      // 给这个元素添加属性 值就是对应的值
      el.setAttribute(key, newProps[key]);
    }
  }
}
复制代码

_update 核心方法就是 patch 初始渲染和后续更新都是共用这一个方法 只是传入的第一个参数不同 初始渲染总体思路就是根据虚拟 dom(vnode) 调用原生 js 方法创建真实 dom 节点并替换掉 el 选项的位置

5._render 和_update 原型方法的混入

// src/index.js

import { initMixin } from "./init.js";
import { lifecycleMixin } from "./lifecycle";
import { renderMixin } from "./render";
// Vue就是一个构造函数 通过new关键字进行实例化
function Vue(options) {
  // 这里开始进行Vue初始化工作
  this._init(options);
}
// _init方法是挂载在Vue原型的方法 通过引入文件的方式进行原型挂载需要传入Vue
// 此做法有利于代码分割
initMixin(Vue);

// 混入_render
renderMixin(Vue);
// 混入_update
lifecycleMixin(Vue);
export default Vue;
复制代码

最后就是把定义在原型的方法引入到 Vue 主文件入口 这样所有的实例都能共享方法了

6.模板编译的思维导图

本文首发于前端黑洞网,博客园同步跟新

(0)

相关推荐

  • 益盟操盘手全套指标公式源码

    {资金博弈 } jgV:=(BIGORDER(1,3)-BIGORDER(2,3))*vol/CAPITAL; dhV:=(BIGORDER(1,2)-BIGORDER(2,2)-BIGORDER(1 ...

  • 「翔博精选指标」拐点主图操盘手无未来公式源码

    做价值的传播者,一路同行,一起成长 适用软件:通达信 公式说明:不包含未来函数,不加密,主图公式 图片: 指标: DRAWKLINE(HIGH,OPEN,LOW,CLOSE); LC:=REF(CLO ...

  • Elasticsearch7.5.0源码编译

    环境及工具 JDK12 Gradle5.6.2 GIT 源码及预处理 到github将代码clone下来,可以根据自己的需求来获取版本,例如 git checkout v7.5.0 提前下载gradl ...

  • 2020年手游陪玩平台源码的开发需要什么功能?

    手游的上分机制成功地吸引了很多用户,像王者.吃鸡等游戏的排位上分牵动着很多玩家的心,在这种情况下,陪玩.代打.陪聊等服务业务也在手游端市场上发展,在这个混乱时期,陪玩APP开发也是顺应了时代的发展.2 ...

  • TelloPy-develop-0.7.0源码阅读.1

    最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下.应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 .在读源码的选择上,我没有选择太复杂的开源库, ...

  • Betaflight飞控1.0源码位置

    玩穿越机的,怎么也听过所谓的bf飞控,也就是"Betaflight",下面的链接给出官方的网址. https://betaflight.com/ 就是这个黑色的蜜蜂Logo 一些特 ...

  • 专业角度告诉你,带货直播源码三个开发重点

    直播行业随我国互联网的快速发展也跟着崛起,并且借助于市场用户群体不断扩大,需求也在增加,给了直播行业充分的发展机遇,尤其是直播带货行业,不仅为电商企业提供了新的销售渠道,也促进了更多实体经济快速转型, ...

  • 姥姥的麻辣烫手写笔记,用了三十年老配方永...

    姥姥的麻辣烫手写笔记,用了三十年老配方永...

  • 一对一直播源码|技术上、功能上优势俱现

    移动互联网的时代里,传统直播仍旧占据了直播社交领域的半壁江山,但是新的直播社交软件,新的创业项目也在不断涌现,对于一对一直播社交软件源码来说无疑是个很好的机会. 对比不同直播的平台,一对一直播系统源码 ...