从0到1实现 mini-vue【patch】

本文详细介绍了迷你版Vue3的patch流程,包括更新节点、新增patch操作,并提供了具体的代码实现。通过阅读,你可以理解Vue3的核心逻辑,包括组件、文本、Fragment节点的处理,以及属性的更新。此外,还提供了测试用例来展示patch的效果。
摘要由CSDN通过智能技术生成

文章标题


前言

patch 流程

在这里插入图片描述

patchChilren 更新节点

节点更新

新增 patch

将src/runtime/render.js mount 版本重命名保存,新建render.js

// import { isBoolean } from '../untils';
import { ShapeFlags } from './vnode';
import { patchProps } from './patchProps';


export function render(vnode,container){
    const prevVnode = container._vnode;
    if(!vnode){
        //n2不存在
        if(prevVnode){
            unmount(prevVnode);
        }
    }else{
        patch(prevVnode,vnode,container);
    }

    // mount(vnode,container);
    container._vnode = vnode;
}

function unmount(vnode){
    const {shapeFlag,el} = vnode;
    if(shapeFlag & ShapeFlags.COMPONENT){
        unmountComponent(vnode);
    }else if(shapeFlag & ShapeFlags.FRAGMENT){
        unmountFragment(vnode);
    }else{
        el.parentNode.removeChild(el);
    }
}

function unmountComponent(vnode){
    

}

function processComponent(n1,n2,container,anchor){

}

/**
 * 删除Fragment节点(el和anchor)
 * @param {*} vnode 
 */
function unmountFragment(vnode){
    let {el:cur,anchor:end} = vnode;
    const {parentNode} = cur;

    while(cur !== end){
        let next = cur.nextSibling;
        parentNode.removeChild(cur);
        cur = next;
    }

    parentNode.removeChild(end);
}

function patch(n1,n2,container,anchor){
    if(n1 && !isSameVNode(n1,n2)){
        anchor =(n1.anchor||n1.el).nextSibling;
        unmount(n1);
        n1 = null;
    }

    const {shapeFlag} = n2;
    if(shapeFlag & ShapeFlags.COMPONENT){
        processComponent(n1,n2,container,anchor);
    }else if(shapeFlag & ShapeFlags.TEXT){
        processText(n1,n2,container,anchor);
    }else if(shapeFlag & ShapeFlags.FRAGMENT){
        processFragment(n1,n2,container,anchor)
    }else{
        processElement(n1,n2,container,anchor)
    }
}

function isSameVNode(n1,n2){
    return n1.type === n2.type;
}

function processText(n1,n2,container,anchor){
    if(n1){
        n2.el =  n1.el;
        n1.el.textContent = n2.children;
    }else{
        mountTextNode(n2,container,anchor)
    }
}

function processFragment(n1,n2,container,anchor){
    //anchor 创建空节点,解决append el位置错乱问题
    // const fragmentStartAnchor = document.createTextNode('');
    // const fragmentEndAnchor = document.createTextNode('');
    const fragmentStartAnchor = n2.el = n1?n1.el:document.createTextNode('');
    const fragmentEndAnchor = n2.el = n1?n1.el:document.createTextNode('');
    n2.anchor = n1?n1.anchor:fragmentEndAnchor;
    if(n1){
        patchChildren(n1,n2,container,fragmentEndAnchor)
    }else{
        // container.appendChild(fragmentStartAnchor)
        // container.appendChild(fragmentEndAnchor)
        container.insertBefore(fragmentStartAnchor,anchor);
        container.insertBefore(fragmentEndAnchor,anchor);
        mountChildren(n2.children,container,fragmentEndAnchor);
    }
}

function processElement(n1,n2,container,anchor){
    if(n1){
        patchElement(n1,n2);
    }else{
        mountElement(n2,container,anchor);
    }
}

/**
 * Text文本节点,使用document.createTextNode创建。type:Symbol props:null children:字符串
 * 例如:
 * {
 *  type:Symbol,
 *  props:null,
 *  children:String,
 * }
 * @param {*} vnode
 * @param {*} container
 */
 function mountTextNode(vnode, container,anchor) {
    const textNode = document.createTextNode(vnode.children);
    // container.appendChild(textNode);
    container.insertBefore(textNode,anchor)
    vnode.el = textNode;
  }


  /**
 * element普通元素,使用document.createElement创建。type:类型,props属性,children子元素(字符串或数组)
 * 例如:
 * {
 *  type:'div',
 *  props:{class:'my-div'},
 *  child:'hello'
 * }
 * @param {*} vnode
 * @param {*} container
 */
function mountElement(vnode, container,anchor) {
    const { type, props, shapeFlag,children } = vnode;
    const el = document.createElement(type);
    // mountProps(props, el);
    patchProps(el,null,props);
    // mountChildren(vnode, el);
    
    // const { shapeFlag, children } = vnode;
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      mountTextNode(vnode, el);
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
     mountChildren(children,el)
    }
    // container.appendChild(el);
    container.insertBefore(el,anchor)
    vnode.el = el;
  }

  function mountChildren(children, container,anchor) {
    children.forEach((child) => {
        // mount(child, container);
        patch(null,child,container,anchor);
      });
  }

// function mountProps(props, el) {
//   for (const key in props) {
//     let value = props[key];
//     switch (key) {
//       case 'class':
//         el.className = value;
//         break;
//       case 'style':
//         for (const styleName in value) {
//           el.style[styleName] = value[styleName];
//         }
//         break;

//       default:
//         if (/^on[^a-z]/.test(key)) {
//           //正则匹配事件,第三个字母不可以为小写
//           const eventName = key.slice(2).toLowerCase();
//           el.addEventListener(eventName, value);
//         } else if (domPropsRE.test(key)) {
//           if (value === '' && isBoolean(el[key])) {
//             value = true;
//           }
//           el[key] = value;
//         } else {
//           if (value == null || value === false) {
//             el.removeAttribute(key);
//           } else {
//             el.setAttribute(key, value);
//           }
//           el.setAttribute(key, value);
//         }
//         break;
//     }
//   }
// }

function patchElement(n1,n2){
    //继承n1
    n2.el = n1.el;
    patchProps(n2.el, n1.props, n2.props);
    patchChildren(n1,n2,n2.el)
}



function unmounChildren(children){
    children.forEach((child)=>{
        unmount(child);
    })
}

function patchChildren(n1, n2, container, anchor) {
    const { shapeFlag: prevShapeFlag, children: c1 } = n1;
    const { shapeFlag, children: c2 } = n2;
  
    //九种情况的判断--对应图解图片文件
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        unmountChildren(c1);
      }
      if (c2 !== c1) {
        container.textContent = c2;
      }
    } else {
      
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          
          
          if (c1[0] && c1[0].key != null && c2[0] && c2[0].key != null) {
            patchKeyedChildren(c1, c2, container, anchor);
          } else {
            patchUnkeyedChildren(c1, c2, container, anchor);
          }
        } else {
         
          unmountChildren(c1);
        }
      } else {
        
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
          container.textContent = '';
        }
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          mountChildren(c2, container, anchor);
        }
      }
    }
  }

  function patchUnkeyedChildren(c1, c2, container, anchor) {
    const oldLength = c1.length;
    const newLength = c2.length;
    const commonLength = Math.min(oldLength, newLength);
    for (let i = 0; i < commonLength; i++) {
      patch(c1[i], c2[i], container, anchor);
    }
    if (newLength > oldLength) {
      mountChildren(c2.slice(commonLength), container, anchor);
    } else if (newLength < oldLength) {
      unmountChildren(c1.slice(commonLength));
    }
  }

function patchArrayChildren(c1,c2,container,anchor){
    const oldLength = c1.length;
    const newLength = c2.length;

    const commonLength = Math.min(oldLength,newLength);
    for (let i = 0; i < commonLength; i++) {
        patch(c1[i],c2[i],container,anchor);
    }

    if(oldLength>newLength){
        unmounChildren(c1.slice(commonLength));

    }else if(oldLength>newLength){
        mountChildren(c2.slice(commonLength),container,anchor);
        
    }


}
新建src/runtime/patchProps.js
export function patchProps(el, oldProps, newProps) {
  if (oldProps === newProps) {
    return;
  }
  //es6默认参数方式传参只有undefined才生效
  oldProps = oldProps || {};
  newProps = newProps || {};
  for (const key in newProps) {
    if (key === 'key') {
      continue;
    }
    const prev = oldProps[key];
    const next = newProps[key];
    if (prev !== next) {
      patchDomProp(el, key, prev, next);
    }
  }
  //删除不存在的属性
  for (const key in oldProps) {
    if (key !== 'key' && !(key in newProps)) {
      patchDomProp(el, key, oldProps[key], null);
    }
  }
}

const domPropsRE = /[A-Z]|^(value|checked|selected|muted|disabled)$/;
function patchDomProp(el, key, prev, next) {
        //   prev{
        //     color:'red'
        //   }
        //   next{
        //     border:'1px solid'
        //   }
  switch (key) {
    case 'class':
      // 暂时认为class就是字符串
      // next可能为null,会变成'null',因此要设成''
      el.className = next || '';
      break;
    case 'style':
      // style为对象
      if (next == null) {
        el.removeAttribute('style');
      } else {
        for (const styleName in next) {
          el.style[styleName] = next[styleName];
        }
        if (prev) {
          for (const styleName in prev) {
            if (next[styleName] == null) {
              el.style[styleName] = '';
            }
          }
        }
      }
      break;
    default:
      if (/^on[^a-z]/.test(key)) {
        //正则匹配事件,第三个字母不可以为小写
        if (prev !== next) {
          const eventName = key.slice(2).toLowerCase();
          if (prev) {
            el.removeEventListener(eventName, prev);
          }
          if (next) {
            el.addEventListener(eventName, next);
          }
        }
      } else if (domPropsRE.test(key)) {
        if (next === '' && typeof el[key] === 'boolean') {
          next = true;
        }
        el[key] = next;
      } else {
        // 例如自定义属性{custom: ''},应该用setAttribute设置为<input custom />
        // 而{custom: null},应用removeAttribute设置为<input />
        if (next == null || next === false) {
          // el.removeAttribute(key);
        } else {
         if(el) el.setAttribute(key, next);
        }
      }
      break;
  }
}

将src/index.js测试用例改为

3秒后进行patch更新

import { reactive } from './reactive/reactive';
import { effect } from './reactive/effect';
import { ref } from './reactive/ref';
import { computed } from './reactive/computed';
import { render, h, Text, Fragment } from './runtime';

//patch
setTimeout(() => {
render(
  h('ul',null,[
  h('li',null,'first'),
  h(Fragment,null,[]),
  h('li',null,'last'),
  ]),
  document.body
);
}, 500);

setTimeout(() => {
  render(
    h('ul',null,[
      h('li',null,'first'),
      h(Fragment,null,[
        h('li',null,'middle')
      ]),
      h('li',null,'last')
    ]),
    document.body
  ) 
}, 3000);



patch 效果

patch更新

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@才华有限公司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值