手写React-key属性

115 阅读1分钟

背景补充

React的列表渲染元素常被要求添加key属性作为元素的唯一标识,它可以告诉React哪些元素发生了修改或删除,而又有哪些元素可以复用,以达到最小化DOM操作的目的。 key属性并非需要全局唯一,只需要在同一父节点下的兄弟节点之间唯一即可,这也侧面说明了key属性是用来识别这种 同一父节点下的类型相同的子节点。

整体思路

主要的关注点是 ==当前要渲染的节点是否是新节点,是否可以复用== 和 ==当前元素的位置是否发生变化== 在diff virtualDOM 和 oldDOM 时,若类型相同,可以通过遍历 oldDOM.childNodes 筛选出原先带key的节点,并存储起来。 在接下来遍历virtualDOM.children时,若子节点存在key属性,则使用这个key去上面的缓存里查找,若能够找到,说明是节点已经存在,只需要进行位置的判断和调整。若找不到,说明是一个新节点,需要新建。

代码实现

// diff.js
// 将oldDOM中带有key属性的子节点存储在一个缓存对象里
const keyedElements = {};
for (let i = 0; i < oldDOM.childNodes.length; i++) {
  const oldChildNode = oldDOM.childNodes[i];
  if (oldChildNode.nodeType === 1) {
    let key = oldChildNode.getAttribute('key');
    if (key) {
      keyedElements[key] = oldChildNode;
    }
  }
}

const hasNoKey = Object.keys(keyedElements).length === 0;

if (hasNoKey) {
  // 元素节点-更新属性
  virtualDOM.children.forEach((child, index) => {
    // 暂时通过index来获取child对应的oldDom
    diff(child, oldDOM, oldDOM.childNodes[index]);
  });
} else {
  // 循环 virtualDOM,通过子节点的key,查找老的子节点
  virtualDOM.children.forEach((child, index) => {
    const key = child.props.key;
    if (key) {
      const targetNode = keyedElements[key];
      if (targetNode) {
        // 位置判断
        if (
          oldDOM.childNodes[index] &&
          oldDOM.childNodes[index] !== targetNode
        ) {
          oldDOM.insertBefore(targetNode, oldDOM.childNodes[index]);
        }
      }
    }
  });
}