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作为key
function 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, ); // ... } // ... }总结