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;
  }
}
相关推荐
雷达学弱狗2 小时前
链式法则解释上游梯度应用
开发语言·前端·javascript
爱隐身的官人3 小时前
爬虫基础学习-爬取网页项目(二)
前端·爬虫·python·学习
Jackson@ML4 小时前
使用字节旗下的TREA IDE快速开发Web应用程序
前端·ide·trea
烛阴6 小时前
解锁 TypeScript 的元编程魔法:从 `extends` 到 `infer` 的条件类型之旅
前端·javascript·typescript
前端开发爱好者7 小时前
弃用 ESLint + Prettier!快 35 倍的 AI 格式化神器!
前端·javascript·vue.js
vivi_and_qiao7 小时前
HTML的form表单
java·前端·html
骑驴看星星a8 小时前
Vue中的scoped属性
前端·javascript·vue.js
四月_h8 小时前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_427506088 小时前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。9 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript