闽公网安备 35020302035485号
App () {
const [list, setList] = useState(['A', 'B', 'C'])
const onClick = () => {
setList(['D'].concat(list))
}
return (
<div>
<button onClick={onClick}>Change</button>
{
list.map((item, idx) => {
return <p id={idx}>{item}</p>
})
}
</div>
)
}
实际发现A,B也重新渲染了,这不是我们希望的。
4.oldFiber 遍历完结束第一轮遍历
// children diff算法入口
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
// ...
// diff算法第一轮遍历, 在这个例子中oldFiber遍历完退出遍历
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// updateSlot 中新旧节点key不同则返回null
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
// 没有newFiber结束第一轮遍历
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
// ...
}
}
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// ...
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
// key相同则复用或更新节点
if (newChild.key === key) {
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
}
}
//堆代码 duidaima.com
}
// ...
return null;
}
id作为keyfunction App () {
const [list, setList] = useState([{ id: 0, val: 'A' }, { id: 1, val: 'B' }, { id: 2, val: 'C' }])
const onClick = () => {
setList([{id: 3, val: 'B'}].concat(list))
}
return (
<div>
<button onClick={onClick}>Change</button>
{
list.map(item => {
return <p key={item.id}>{item.val}</p>
})
}
</div>
)
}
我们点击button,发现只有D节点是新增的,A,B节点是复用之前的,这才是我们期望的。
// updateSlot 中新旧节点key不同则返回null
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
// 没有newFiber结束第一轮遍历
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
走到后面复用的逻辑,所有的oldFiber节点生成一个map,为了快速查找把key作为索引。updateFromMap会优先复用existingChildren里的节点,没有复用的就创建新节点,因此这里A,B,C节点是可以复用的,D节点是新创建的。这就从源码层面解释了只有D节点是变化的。function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
// ...
// 所有的oldFiber 生成一个map, 为了快速查找把key作为索引
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
// 在existingChildren中查找可以复用的节点,找不到就重新生成
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
// ...
}
// ...
}
总结