前言
patch 流程
patchChilren 更新节点
新增 patch
将src/runtime/render.js mount 版本重命名保存,新建render.js |
import { ShapeFlags } from './vnode';
import { patchProps } from './patchProps';
export function render(vnode,container){
const prevVnode = container._vnode;
if(!vnode){
if(prevVnode){
unmount(prevVnode);
}
}else{
patch(prevVnode,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){
}
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){
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.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);
}
}
function mountTextNode(vnode, container,anchor) {
const textNode = document.createTextNode(vnode.children);
container.insertBefore(textNode,anchor)
vnode.el = textNode;
}
function mountElement(vnode, container,anchor) {
const { type, props, shapeFlag,children } = vnode;
const el = document.createElement(type);
patchProps(el,null,props);
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
mountTextNode(vnode, el);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children,el)
}
container.insertBefore(el,anchor)
vnode.el = el;
}
function mountChildren(children, container,anchor) {
children.forEach((child) => {
patch(null,child,container,anchor);
});
}
function patchElement(n1,n2){
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;
}
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) {
switch (key) {
case 'class':
el.className = next || '';
break;
case '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 {
if (next == null || next === false) {
} else {
if(el) el.setAttribute(key, next);
}
}
break;
}
}
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';
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 效果