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;
  }
}
相关推荐
深念Y10 分钟前
我明白为什么B站没法在浏览器开直播了——Windows Chrome推流踩坑全记录
前端·chrome·webrtc·浏览器·srs·直播·flv
zhangxingchao21 分钟前
AI应用开发七:可以替代 RAG 的技术
前端·人工智能·后端
Sun@happy36 分钟前
现代 Web 前端渗透——基础篇(1)
前端·web安全
希冀1231 小时前
【CSS学习第十一篇】
前端·css·学习
隔窗听雨眠1 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端
kyriewen1 小时前
写组件文档写到吐?我用AI自动生成Storybook,同事以后直接抄
前端·javascript·面试
excel1 小时前
🧠 Prisma 表名大写 vs SQL 导出小写问题深度解析(附踩坑与解决方案)
前端·后端
周淳APP2 小时前
【前端工程化原理通识:从源头到运行时的理论阐述】
前端·编译·打包·前端工程化
五点六六六2 小时前
你敢信这是非Native页面写出来的渐变效果吗🌝(底层原理解析
前端·javascript·面试
tedcloud1232 小时前
TradingAgents部署教程:打造AI量化分析工作流
服务器·前端·人工智能·系统架构·edge