React 实现节点删除

今天带大家实现节点删除。

先看个例子。

js 复制代码
function FunctionComponent() {
    const [count1, setCount1] = useReducer((x) => x + 1, 0);

    return (
        <div>
            {
                count1 % 2 === 0 ? (
                    <button onClick={() => {setCount1()}}>{count1}</button>
                ) : (<span>react</span>)
            }
        </div>
    )
}

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render((<FunctionComponent />) as any);

思路:

  1. 节点删除包含两个部分:vdom 删除(不用做),dom 删除
  2. 如何删除 dom?把所有要删除的节点放在父 fiber 上

Render 阶段

修改 reconcileSingleElement 函数,实现删除单个节点 deleteChild 和删除多个节点 deleteRemainingChildren

js 复制代码
  function deleteChild(returnFiber: Fiber, childToDelete: Fiber) {
    // 初次渲染
    if (!shouldTrackSideEffects) {
      return;
    }
    
    // 把所有要删除的节点放在父 fiber 上
    const deletions = returnFiber.deletions;
    if (deletions === null) {
      returnFiber.deletions = [childToDelete];
      returnFiber.flags |= ChildDeletion;
    } else {
      returnFiber.deletions!.push(childToDelete);
    }
  }
  
  // 删除多个节点
  function deleteRemainingChildren(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null
  ) {
    if (!shouldTrackSideEffects) {
      return;
    }

    // 初始值
    let childToDelete = currentFirstChild;
    while (childToDelete !== null) {
      deleteChild(returnFiber, childToDelete);
      childToDelete = childToDelete.sibling;
    }

    return null;
  }

// 协调单个节点,对于页面初次渲染,创建 fiber,不涉及对比复用老节点
// new (1)
// old 2 [1] 3 4
function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement
) {
    // 节点复用的条件
    // 1. 同一层级下 2. key 相同 3. 类型相同
    // element 和 currentFirstChild 对应同一个父级,第一个条件满足
    const key = element.key;
    let child = currentFirstChild;

    while (child !== null) {
      if (child.key === key) {
        const elementType = element.type;
        // 可以复用
        if (child.elementType === elementType) {
          // todo 后面其它 fiber 可以删除了
          
          const existing = useFiber(child, element.props);
          existing.return = returnFiber;
          return existing;
        } else {
          // 前提:React 不认为同一层级下有两个相同的 key 值
          deleteRemainingChildren(returnFiber, child);
          break;
        }
      } else {
        // 删除单个节点
        deleteChild(returnFiber, child);
      }
      // 老 fiber 节点是单链表
      child = child.sibling;
    }

    let createdFiber = createFiberFromElement(element);
    createdFiber.return = returnFiber;
    return createdFiber;
}

Commit 阶段

commitReconciliationEffects 增加删除的 case。

js 复制代码
// fiber.stateNode 是 DOM 节点
export function isHost(fiber: Fiber): boolean {
  return fiber.tag === HostComponent || fiber.tag === HostText;
}

// 获取子 dom 节点
function getStateNode(fiber: Fiber) {
  let node = fiber;
  while (1) {
    if (isHost(node) && node.stateNode) {
      return node.stateNode;
    }
    node = node.child as Fiber;
  }
}

// 根据 fiber 删除 dom 节点,必须有父 dom、子 dom
// 删除多个 dom 节点
function commitDeletions(
  deletions: Array<Fiber>,
  parentDOM: Element | Document | DocumentFragment
) {
  deletions.forEach((deletion) => {
    parentDOM.removeChild(getStateNode(deletion));
  });
}

function commitReconciliationEffects(finishedWork: Fiber) {
  const flags = finishedWork.flags;
  if (flags & Placement) {
    // 页面初次渲染 新增插入 appendChild
    // todo 页面更新,修改位置 appendChild || insertBefore
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  // 删除
  if (flags & ChildDeletion) {
    // parentFiber 是 deletions 的父 dom 对应的 fiber
    const parentFiber = isHostParent(finishedWork)
      ? finishedWork
      : getHostParentFiber(finishedWork);
    const parentDOM = parentFiber.stateNode;
    commitDeletions(finishedWork.deletions!, parentDOM);
    finishedWork.flags &= ~ChildDeletion;
    finishedWork.deletions = null;
  }
}
相关推荐
七夜zippoe几秒前
前端开发中的难题及解决方案
前端·问题
Hockor1 小时前
用 Kimi K2 写前端是一种什么体验?还支持 Claude Code 接入?
前端
杨进军1 小时前
React 实现 useMemo
前端·react.js·前端框架
海底火旺1 小时前
浏览器渲染全过程解析
前端·javascript·浏览器
你听得到111 小时前
揭秘Flutter图片编辑器核心技术:从状态驱动架构到高保真图像处理
android·前端·flutter
驴肉板烧凤梨牛肉堡1 小时前
浏览器是否支持webp图像的判断
前端
Xi-Xu1 小时前
隆重介绍 Xget for Chrome:您的终极下载加速器
前端·网络·chrome·经验分享·github
摆烂为不摆烂1 小时前
😁深入JS(九): 简单了解Fetch使用
前端
杨进军1 小时前
React 实现多个节点 diff
前端·react.js·前端框架
用户40812812003811 小时前
拓展运算符和剩余参数
前端