【Vue】实现一个miniVue之渲染系统模块

为了对Vue有进一步的了解,实现一个简洁版的Mini-Vue框架,主要包括三个模块:

  • 渲染系统模块
  • 可响应式系统模块
  • 应用程序入口模块

本文章主要是书写渲染系统模块
该模块主要包括三个功能:

  • h函数,用于返回一个VNode对象
  • mount函数,用于将Vnode挂载到DOM上
  • patch函数,用于对两个VNode进行对比,决定如何处理新的VNode

1. 创建index.html文件,在文件下通过h函数来创建一个vnode

const vnode = h('div',{class:"why",id:"aaa"},[
h("h2",null,"当前计数:100"),
h("button", {onClick: function() {}}, "+1")
])

2. 创建render.js,在html文件中导入,书写h函数

<script src="./renderer.js"></script>
  • 注意到h函数具有三个参数,分别是元素标签名、元素属性、孩子节点数,并且返回的是一个对象
const h (tag,props,children) =>{
// vnode -> javascript对象 -> {}
	return {
		tag,
		props,
		children
	}
}

在这里插入图片描述

3. 通过mount函数, 将vnode挂载到div#app

  • 在index.html文件中
mount(vnode,document.querySelector("#app")
  • 书写mount函数
  • 注意到函数存在两个参数,分别是需要挂载的vnode的节点,和挂载的元素
  • 书写mount函数流程图
Created with Raphaël 2.3.0 根据tag,创建HTML元素,并且存储 到vnode的el中 处理props属性 是否是事件元素(以on开头)? 监听事件 处理子节点 是否是字符串节点? 直接设置textContent 遍历调用 mount 函数 通过 setAttribute 添加 yes no yes no
const mount = (vnode,container)=>{
	// vnode -> element
  	// 1.创建出真实的原生, 并且在vnode上保留el
	let el = vnode.el = document.createElement(vnode.tag)

	// 2.处理props
	if(vnode.props){
		for(const key in props){
			const value = vnode.props[key] //key :class value : why
			//注意props中可能存在事件
			if(key.startWith("on")){
				el.addEventListener(key.slice(2).toLowerCase(),value)
			}else{
				el.setAttribute(key,value)
			}
		}
	}

	// 3.处理children
	if(vnode.children){
		if(typeof vnode.children === 'string'){
			el.textContent = vnode.children
		}else{
			vnode.children.forEach(item=>{
				mount(item, el);
			})
		}
	}

	// 4.将el挂载到container上
  container.appendChild(el);
}
  • 此时可以发现,页面渲染成功
    在这里插入图片描述

4. 创建新的vnode,通过patch函数进行对比更新

  • 在index.html文件中创建新的vnode
const vnode1 = h('div', {class: "reason", id: "aaa"}, [
        h("h2", null, "呵呵呵"),
        h("button", {onClick: function() {}}, "-1")
      ]); 
patch(vnode, vnode1);
  • patch函数的书写要考虑三种情况
const patch = (n1, n2) => {
  if (n1.tag !== n2.tag) {
    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.startsWith("on")) { // 对事件监听的判断
        const value = oldProps[key];
        el.removeEventListener(key.slice(2).toLowerCase(), value)
      } 
      if (!(key in newProps)) {
        el.removeAttribute(key);
      }
    }

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

    if (typeof newChidlren === "string") { // 情况一: newChildren本身是一个string
      // 边界情况 (edge case)
      if (typeof oldChildren === "string") {
        if (newChidlren !== oldChildren) {
          el.textContent = newChidlren
        }
      } else {
        el.innerHTML = newChidlren;
      }
    } else { // 情况二: newChildren本身是一个数组
      if (typeof oldChildren === "string") {
        el.innerHTML = "";
        newChidlren.forEach(item => {
          mount(item, el);
        })
      } else {
        // oldChildren: [v1, v2, v3, v8, v9]
        // newChildren: [v1, v5, v6]
        // 1.前面有相同节点的原生进行patch操作
        const commonLength = Math.min(oldChildren.length, newChidlren.length);
        for (let i = 0; i < commonLength; i++) {
          patch(oldChildren[i], newChidlren[i]);
        }

        // 2.newChildren.length > oldChildren.length
        if (newChidlren.length > oldChildren.length) {
          newChidlren.slice(oldChildren.length).forEach(item => {
            mount(item, el);
          })
        }

        // 3.newChildren.length < oldChildren.length
        if (newChidlren.length < oldChildren.length) {
          oldChildren.slice(newChidlren.length).forEach(item => {
            el.removeChild(item.el);
          })
        }
      }
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值