实现DOM diff的update
On this page
大家好,我是万维读客的讲师曹欢欢。上节课我们实现了DOM节点新增的diff操作,本节我们看看如何实现update操作。
DOM的UPDATE操作
在写代码之前呢,我们先思考下DOM节点更新要处理哪些事情? 大致可以分为哪些情况?我们需要判断新增、变化、删除这几种情况。
新增:旧的没有,新的有
变化:旧的有,新的有,新的和旧的不一样
删除:旧的有,新的没有
对于DOM节点的操作其实是一样的,要具体处理的事情我们列一下:
1. 事件处理,删除旧的有新的没有的事件,删除变化的事件, 设置新增或者变化的事件
2. 属性处理,删除旧的有新的没有的属性,设置新增的或者变化的属性
实现DOM的Update
按照上面的逻辑,我们可以来编写代码,在commitDOM中增加UPDATE的场景处理,新增updateDom函数,统一对属性和事件做处理,代码如下:
const isNew = (old, next)=>(key)=> !(key in old.props) && (key in next.props);
const isChanged = (old, next)=>(key)=> (key in old.props) && (key in next.props) && old.props[key] != next.props[key];
const isDelete = (old, next)=>(key)=> (key in old.props) && !(key in next.props) ;
...
function commitDOM(fiber) {
let tempParenNode = null;
if (fiber.return && fiber.stateNode) {
tempParenNode = fiber.return;
while (!tempParenNode.stateNode) {
tempParenNode = tempParenNode.return;
}
}
if (tempParenNode && fiber.effectTag === 'PLACEMENT') {
updateDom({props:{}}, fiber);
tempParenNode.stateNode.appendChild(fiber.stateNode);
}else if(tempParenNode && fiber.effectTag === 'UPDATE'){
updateDom(fiber.alternate, fiber);
}
if (fiber.child) {
commitDOM(fiber.child);
}
if (fiber.sibling) {
commitDOM(fiber.sibling);
}
}
...
function updateDom(pre, next){
//事件处理
Object.keys(pre.props).filter(isEvent).filter(
key=> isDelete(pre, next)(key) || isChanged(pre, next)(key)
).forEach(key => {
const eventName = key.toLowerCase().substring(2);
next.stateNode.removeEventListener(eventName, next.props[key]);
});
Object.keys(next.props).filter(isEvent).filter(
key=> isNew(pre, next)(key) || isChanged(pre, next)(key)
).forEach(key => {
const eventName = key.toLowerCase().substring(2);
next.stateNode.addEventListener(eventName, next.props[key]);
});
// 属性处理
Object.keys(pre.props).filter(filerProps).filter(
key=> isDelete(pre, next)(key)
).forEach(key => {
next.stateNode[key] = '';
});
Object.keys(next.props).filter(filerProps).filter(
key=> isNew(pre, next)(key) || isChanged(pre, next)(key)
).forEach(key => {
next.stateNode[key] = next.props[key];
});
}
查看我们上上节课增加的测试用例,可以看到测试用例全部通过了,文本节点update正常。
stdout | treact06/jsx.test.jsx > event handler test > render and commit
container.innerHTML <div class="button">2<ol><li>0</li><li>1</li></ol></div>
✓ treact06/jsx.test.jsx (5)
✓ async render Function Component (1)
✓ render with act
✓ fiber useState test (1)
✓ render useState
✓ fiber useReducer test (1)
✓ render useReducer
✓ event handler test (2)
✓ add event
✓ render and commit
Test Files 1 passed (1)
Tests 5 passed (5)
Start at 14:01:26
Duration 49ms
参考
- DOM DIFF文档:https://zhuanlan.zhihu.com/p/362539108