首页 > 其他分享 >vue渲染原理简单实现

vue渲染原理简单实现

时间:2023-07-05 10:23:37浏览次数:39  
标签:el vue const 渲染 vnode key newChildren 原理 oldChildren

实现功能:
1.渲染系统:
· 功能一:h函数,用于创建并返回一个VNode(虚拟对象);
· 功能二:mount函数,用于将VNode挂载到节点上;
· 功能三: patch函数,用于对比两个VNode,决定该如何处理新的VNode;

1.新建一个index.html的页面

其中有一个id为app的div元素,之后我们写的所有DOM都会挂载到此元素下;然后引用了一个名为renderer.js的js文件,用来实现虚拟DOM转化和真实挂载。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script src="./renderer.js"></script>
    <script>
         //h函数和mount函数存储在renderer.js中

        // 1.通过h函数来创建一个vnode
        const vnode = h('div', { class: 'why' }, [
            h('h2', null, "当前计数:100"),
            h('button', {onClick:()=>{console.log("+1")}}, "+1"),
        ]);

        // 通过mount函数,将vnode挂载到div#app上
        mount(vnode,document.querySelector("#app"));
    </script>
</body>
</html>

2.编写renderer.js文件

  1. 其中h()函数用于产生虚拟DOM,他将传入的标记属性子元素集成在一个对象之中,然后返回,产生如图的一个javaScript对象

2.mount()函数的作用是将vnode(虚拟节点)转化成真实DOM然后挂载到对应的容器。

//转化成虚拟DOM
const h = (tag, props, children) => {
  return {
    tag,
    props,
    children,
  };
};

//递归挂载
const mount = (vnode, container) => {
  // vnode ->element
  //1.创建出真实DOM,并在vnode上保留el
  const el = (vnode.el = document.createElement(vnode.tag));
  //2.处理props
  if (vnode.props) {
    for (const key in vnode.props) {
      const value = vnode.props[key];
      //对事件监听的判断
      if (key.startsWith("on")) {
        el.addEventListener(key.slice(2).toLowerCase(), value);
      }
      el.setAttribute(key, value);
    }
  }

  //3. 处理children
  if (vnode.children) {
    //如果children的内容直接是字符串
    if(typeof vnode.children === 'string'){
        el.textContent = vnode.children;
    }else{
        //如果children的内容是数组,即此元素还拥有子元素,递归调用mount
        vnode.children.forEach(item => {
            mount(item,el);
        });
    }
  }

  //4.将el挂载到container中
  container.appendChild(el);
};

通过调用这两个函数,我们即实现了将虚拟DOM转化成真实DOM,并显示到浏览器界面中的功能.

3.模拟页面元素更新时的流程

当页面中的元素更新的时候,通过调用patch()函数来对比前一次和后一次的VNode,添加不同的边界判断来区分各种情况,代码如下:

const patch = (n1, n2) => {
  //判断两个元素的类型
  if (n1.tag !== n2.tag) {
    //如果节点的类型不相同则没有比较的必要,那么可以直接移除n1,挂载n2
    const n1ElParent = n1.el.parentElement;
    n1ElParent.removeChild(n1.el);
    mount(n2, n1ElParent);
  } else {
    // 1. 取出element对象,并且在n2中进行保存
    const el = (n2.el = n1.el);

    //2.处理props
    const oldProps = n1.props || {};
    const newProps = n2.props || {};
    //2.1获取所有newProps添加到el
    for (const key in newProps) {
      const oldValue = oldProps[key];
      const newValue = newProps[key];
      if (newValue !== oldValue) {
        if (key.startsWith("on")) {
          //对事件监听的判断
          el.addEventListener(key.slice(2).toLowerCase(), newValue);
        } else {
          el.setAttribute(key, newValue);
        }
      }
    }
    //2.2删除旧的props
    for (const key in oldProps) {
      //如果旧属性名称没有在新属性名称中出现,则删除旧的属性
      if (!(key in newProps)) {
        if (key.startsWith("on")) {
          const value = oldProps[key];
          el.removeEventListener(key.slice(2).toLowerCase(), value);
        } else {
          el.removeAttribute(key);
        }
      }
    }

    //3.处理children
    const oldChildren = n1.children || [];
    const newChildren = n2.children || [];

    if (typeof newChildren === "string") {
      //边界判断
      if (typeof oldChildren === "string") {
        if (newChildren !== oldChildren) {
          el.textContent = newChildren;
        }
      } else {
        el.innerHTML = newChildren;
      }
    } else {
      //newChildren是一个数组
      if (typeof oldChildren === "string") {
        //oldChildren是个字符串
        el.innerHTML = "";
        newChildren.forEach((item) => {
          mount(item, el);
        });
      } else {
        // oldChildren和newChildren都不是string

        //以最短方式再进行比对替换操作 (长度相等的情况下)
        const commonLength = Math.min(oldChildren.length, newChildren.length);
        //因为递归调用,倒置oldChildren
        oldChildren.reverse();
        for (let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChildren[i]);
        }
        //newChildren.length > oldChildren.length
        //把newCildren中比oldChildren多出来的部分直接挂载到el上
        if (newChildren.length > oldChildren.length) {
          newChildren.slice(oldChildren.length).forEach((item) => {
            mount(item, el);
          });
        }
        //newChildren.length < oldChildren.length
        //新节点数组长度小于旧节点数组长度,需要移除旧节点数组中的中的部分节点
        if (newChildren.length < oldChildren.length) {
          oldChildren.slice(newChildren.length).forEach((item) => {
            el.removeChild(item.el);
          });
        }
      }
    }
  }
};

index.html中做出对应更改,来模拟DOM的更新:

setTimeout(() => {
            // 3.创建新的VNode
            const vnode1 = h('div', { class: 'coderWhy', id: "aaaa" }, [
                h('h3', { class: 'myTitle' }, "这是我的标题"),
                h('button', { onClick: console.log("新的打印函数") }, "打印"),
                h('button', { onClick: console.log("新的打印函数") }, "打印2"),
                h('button', { onClick: console.log("新的打印函数") }, "打印3"),
            ]);
            patch(vnode, vnode1);
        }, 2000);

标签:el,vue,const,渲染,vnode,key,newChildren,原理,oldChildren
From: https://www.cnblogs.com/sunyan97/p/17525514.html

相关文章

  • Vue项目引入Bootstrap5步骤
    1、在工程项目下安装Bootstrap5依赖包[email protected]或者[email protected]、安装安装jqueryBootstrap有js函数,必须新引入jquerynpminstalljquery--save3、在vue.config.js配置jQuery插件的参数module.exports=......
  • Vue, Django | 数据可视化平台开发
    给公司搞了个互联网设备可视化平台,在地图上展示互联网设备的信息,点击地图不同区域,统计相应的设备信息,生成图表展示出来用的vue-big-screen框架,在原框架基础上,主要干了下面几件事:1.下载不同区域的geojson数据,点击大图的不同区域,调用mapclick方法,将子区域的geojson数据加载出来2......
  • 前端Vue自定义轮播图视频播放组件 仿京东商品详情轮播图视频Video播放效果 可图片预览
    前端Vue自定义轮播图视频播放组件仿京东商品详情轮播图视频Video播放,可图片预览,下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13325效果图如下:cc-videoSwiper使用方法<!--goodsData:轮播图视频数据 @setShowVideo:视频按钮点击事件-......
  • 前端Vue一款基于canvas的精美商品海报生成组件 根据个性化数据生成商品海报图 长按
    前端Vue一款基于canvas的精美商品海报生成组件根据个性化数据生成商品海报图长按保存图片,下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13326效果图如下:cc-beautyPoster使用方法<!--posterData:海报数据--><cc-beautyPoster:poste......
  • vue2-样式冲突-使用deep修改子组件中的样式
    /deep/样式穿透<template><divclass="left-container"><h3>Left组件</h3><my-count:init="9"></my-count></div></template><script>exportdefault{}</scrip......
  • vuE初探[230704]
    vue语法初探课前须知知识储备:html、JavaScript、css(node、npm、webpack)课程介绍进阶式:基础:生命周期函数条件循环渲染指令、页面样式修饰语法···组件:全局&局部、数据传递、插槽基础···动画:单组件元素、列表&状态动画、CSS和JS动画···高级扩展语法:Mixin混入、V......
  • Vue02
    1.Vue计算属性和watch监听1.1.计算属性<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title><!--引入vue.js--><scriptsrc="node_modules/vue/dist/vue......
  • vue项目中锚点定位bug无效和替代方式
    在vue项目中,使用锚点定位会和router的规则冲突,在项目中的表现就是第一次点击url改变了,但是没有跳转到锚点位置,再次点击才会跳转。所以在vue项目中定义一个方法不适用锚点定位:scrollToSection(id){letsection=document.getElementById(id)if(section){......
  • 解决vue 不是内部或外部命令
    1.输入命令找到npm的配置路径npmconfiglist2.查看此路径下有没有vue.cmd3.如果有vue.cmd,将当前路径复制添加到path环境变量步骤:桌面右击“我的电脑”-属性-高级系统设置-环境变量环境变量两种添加方式:①直接新建-规范取一个变量名-将vue.cmd所在路径复制到变量值......
  • mybatis的原理
    MyBatis是一个持久层框架,它的工作原理是:1.首先,MyBatis从XML映射文件中读取SQL语句,将其解析成SQL语句对象,并将SQL语句对象存储在内存中。2.然后,MyBatis将SQL语句对象与用户提供的参数绑定,生成完整的SQL语句。3.接着,MyBatis将SQL语句发送给数据库,并将查询结果映射到Jav......