react基础2
魔幻的key
key在vue和react中都有用到,都是用来diff的,那么key到底在diff中起到了一个什么样的作用呢?
以下是vue官网中对于key的解释。
Vue 默认按照“就地更新”的策略来更新通过
v-for
渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染
所以如果列表渲染中,每个列表项的dom都不大一样或者非常简单的时候,不用key可以减少dom的重建,但是如果列表项中包含子组件,子组件又依赖父组件,不添加key的情况,很容易造成列表变化没有引起diff重新渲染。
react diff 过程
react中的diff又叫reconcile.即协调
对不同的节点有不同的协调过程:
单节点
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 从左到右的遍历
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
// switch case
} else {
deleteChild(returnFiber, child);
}
// 下一个节点
child = child.sibling;
}
// 创建新Fiber,并返回
数组
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>,
lanes: Lanes,
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// old和new均没有到达尾部的时候
for (; oldFiber !== null && newIdx < newChildren.length; newIdx ) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber; // 如果是当前的older > newArray,则直接断开链表,没必要继续比较了。
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// 更新fiber, updateSlot 在key不相等或不存在的时候会返回null,否则会返回一个fiber
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
// 无可复用的fibber,break循环
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
// 如果有sideEffect
if (shouldTrackSideEffects) {
// 旧的子元素 Fiber 链表中是否存在当前位置的 Fiber 对象,
// 同时新的 Fiber 对象的 `alternate` 属性是否为 `null`。
// 如果这两个条件都满足,说明当前位置的 Fiber 对象没有被重用,需要将其从 Fiber 树中删除。
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
// 记录比较的位置
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// 新链表的头指针记录,只有第一个元素会走到这
resultingFirstChild = newFiber;
} else {
// 新链表的头指针,指向下一个位置,新增
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// newArray已经走完了,删除fiber中剩余的元素
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
if (oldFiber === null) {
// 如果oldFiber没走,则需要新增newArray中的数据到previousNewFiber
for (; newIdx < newChildren.length; newIdx ) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
// newArray 和OldFiber都没走完,将fiber变成key-fiber的一个map
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx ) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
从上面的源码可以看出,在diff的过程中,发生了两次遍历,第一次是将newArray和oldFiber进行比较,在比较的过程中同时会生成一个newFiber:
- 如果currentFiber和currentArrayItem相等,则复用fiber并将链表和数组的指针各向后移动一位,并且将fiber挂载新的resultingFirstChild上。
- 如果不相等,没有可复用的fiber,则直接跳出循环
- 如果newArray走完了,则说明有需oldFiber中有需要删除的节点,剩余节点全部删除
- 如果oldFiber走完了,说明newArray的剩余节点需要增加,把newArray的节点挂载到resultingFirstChild上。
- 两个都没走完则开始进行diff最精髓的比较:继续遍历剩下的数据,同样的,每次只比较一个newArray中的数据和一个oldFiber链表上的数据,只是通过用key来获取currentFiber,剩下的逻辑与reconcile的逻辑类似。
比较
相同点:
没有key会默认使用index作为key,则发生元素删除活添加的时候,会重新渲染整个列表,而不是只渲染删除或者增加的元素
不同点:
react中的链表是单链表,有一个指针指向兄弟节点,一个指针指向父亲节点,diff的过程只能单向遍历。
vue中的链表是双向链表,所以可以进行首尾遍历,进行优化。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgahkba
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13