经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
前端进阶-手写Vue2.0源码(三)|技术点评
来源:cnblogs  作者:python之恋  时间:2021/3/8 13:51:24  对本文有异议

前言

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

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

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

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


正文

1.组件挂载入口

  1. // src/init.js
  2. Vue.prototype.$mount = function (el) {
  3. const vm = this;
  4. const options = vm.$options;
  5. el = document.querySelector(el);
  6. // 如果不存在render属性
  7. if (!options.render) {
  8. // 如果存在template属性
  9. let template = options.template;
  10. if (!template && el) {
  11. // 如果不存在render和template 但是存在el属性 直接将模板赋值到el所在的外层html结构(就是el本身 并不是父元素)
  12. template = el.outerHTML;
  13. }
  14. // 最终需要把tempalte模板转化成render函数
  15. if (template) {
  16. const render = compileToFunctions(template);
  17. options.render = render;
  18. }
  19. }
  20. // 将当前组件实例挂载到真实的el节点上面
  21. return mountComponent(vm, el);
  22. };
  23. 复制代码

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

2.组件挂载核心方法 mountComponent

  1. // src/lifecycle.js
  2. export function mountComponent(vm, el) {
  3. // 上一步模板编译解析生成了render函数
  4. // 下一步就是执行vm._render()方法 调用生成的render函数 生成虚拟dom
  5. // 最后使用vm._update()方法把虚拟dom渲染到页面
  6. // 真实的el选项赋值给实例的$el属性 为之后虚拟dom产生的新的dom替换老的dom做铺垫
  7. vm.$el = el;
  8. // _update和._render方法都是挂载在Vue原型的方法 类似_init
  9. vm._update(vm._render());
  10. }
  11. 复制代码

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

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

  1. // src/render.js
  2. import { createElement, createTextNode } from "./vdom/index";
  3. export function renderMixin(Vue) {
  4. Vue.prototype._render = function () {
  5. const vm = this;
  6. // 获取模板编译生成的render方法
  7. const { render } = vm.$options;
  8. // 生成vnode--虚拟dom
  9. const vnode = render.call(vm);
  10. return vnode;
  11. };
  12. // render函数里面有_c _v _s方法需要定义
  13. Vue.prototype._c = function (...args) {
  14. // 创建虚拟dom元素
  15. return createElement(...args);
  16. };
  17. Vue.prototype._v = function (text) {
  18. // 创建虚拟dom文本
  19. return createTextNode(text);
  20. };
  21. Vue.prototype._s = function (val) {
  22. // 如果模板里面的是一个对象 需要JSON.stringify
  23. return val == null
  24. ? ""
  25. : typeof val === "object"
  26. ? JSON.stringify(val)
  27. : val;
  28. };
  29. }
  30. 复制代码

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

  1. // src/vdom/index.js
  2. // 定义Vnode类
  3. export default class Vnode {
  4. constructor(tag, data, key, children, text) {
  5. this.tag = tag;
  6. this.data = data;
  7. this.key = key;
  8. this.children = children;
  9. this.text = text;
  10. }
  11. }
  12. // 创建元素vnode 等于render函数里面的 h=>h(App)
  13. export function createElement(tag, data = {}, ...children) {
  14. let key = data.key;
  15. return new Vnode(tag, data, key, children);
  16. }
  17. // 创建文本vnode
  18. export function createTextNode(text) {
  19. return new Vnode(undefined, undefined, undefined, undefined, text);
  20. }
  21. 复制代码

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

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

  1. // src/lifecycle.js
  2. import { patch } from "./vdom/patch";
  3. export function lifecycleMixin(Vue) {
  4. // 把_update挂载在Vue的原型
  5. Vue.prototype._update = function (vnode) {
  6. const vm = this;
  7. // patch是渲染vnode为真实dom核心
  8. patch(vm.$el, vnode);
  9. };
  10. }
  11. 复制代码
  1. // src/vdom/patch.js
  2. // patch用来渲染和更新视图 今天只介绍初次渲染的逻辑
  3. export function patch(oldVnode, vnode) {
  4. // 判断传入的oldVnode是否是一个真实元素
  5. // 这里很关键 初次渲染 传入的vm.$el就是咱们传入的el选项 所以是真实dom
  6. // 如果不是初始渲染而是视图更新的时候 vm.$el就被替换成了更新之前的老的虚拟dom
  7. const isRealElement = oldVnode.nodeType;
  8. if (isRealElement) {
  9. // 这里是初次渲染的逻辑
  10. const oldElm = oldVnode;
  11. const parentElm = oldElm.parentNode;
  12. // 将虚拟dom转化成真实dom节点
  13. let el = createElm(vnode);
  14. // 插入到 老的el节点下一个节点的前面 就相当于插入到老的el节点的后面
  15. // 这里不直接使用父元素appendChild是为了不破坏替换的位置
  16. parentElm.insertBefore(el, oldElm.nextSibling);
  17. // 删除老的el节点
  18. parentElm.removeChild(oldVnode);
  19. return el;
  20. }
  21. }
  22. // 虚拟dom转成真实dom 就是调用原生方法生成dom树
  23. function createElm(vnode) {
  24. let { tag, data, key, children, text } = vnode;
  25. // 判断虚拟dom 是元素节点还是文本节点
  26. if (typeof tag === "string") {
  27. // 虚拟dom的el属性指向真实dom
  28. vnode.el = document.createElement(tag);
  29. // 解析虚拟dom属性
  30. updateProperties(vnode);
  31. // 如果有子节点就递归插入到父节点里面
  32. children.forEach((child) => {
  33. return vnode.el.appendChild(createElm(child));
  34. });
  35. } else {
  36. // 文本节点
  37. vnode.el = document.createTextNode(text);
  38. }
  39. return vnode.el;
  40. }
  41. // 解析vnode的data属性 映射到真实dom上
  42. function updateProperties(vnode) {
  43. let newProps = vnode.data || {};
  44. let el = vnode.el; //真实节点
  45. for (let key in newProps) {
  46. // style需要特殊处理下
  47. if (key === "style") {
  48. for (let styleName in newProps.style) {
  49. el.style[styleName] = newProps.style[styleName];
  50. }
  51. } else if (key === "class") {
  52. el.className = newProps.class;
  53. } else {
  54. // 给这个元素添加属性 值就是对应的值
  55. el.setAttribute(key, newProps[key]);
  56. }
  57. }
  58. }
  59. 复制代码

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

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

  1. // src/index.js
  2. import { initMixin } from "./init.js";
  3. import { lifecycleMixin } from "./lifecycle";
  4. import { renderMixin } from "./render";
  5. // Vue就是一个构造函数 通过new关键字进行实例化
  6. function Vue(options) {
  7. // 这里开始进行Vue初始化工作
  8. this._init(options);
  9. }
  10. // _init方法是挂载在Vue原型的方法 通过引入文件的方式进行原型挂载需要传入Vue
  11. // 此做法有利于代码分割
  12. initMixin(Vue);
  13. // 混入_render
  14. renderMixin(Vue);
  15. // 混入_update
  16. lifecycleMixin(Vue);
  17. export default Vue;
  18. 复制代码

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

6.模板编译的思维导图

初始渲染

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

原文链接:http://www.cnblogs.com/pythonzhilian/p/14499081.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号