1. 传统 DOM 操作丢失元素状态
自 1998 年推出第一个 DOM 标准草案以来,文档对象模型(DOM)就只有移除和插入原语。因此,每当开发者将 DOM 中的某个元素从一个地方 “移动” 到另一个地方时,实际上是在后台进行移除和插入操作。例如,典型的 appendChild() 或 insertBefore() API 都是先从旧父元素中移除元素,然后再将其重新插入到新元素。
因此,所谓的 “移动” 操作实际上是在 “移除和插入”,而这通常不会影响用户体验。例如,在 DOM 中 “移动”
时,这两个操作不会产生任何破坏性副作用,但在移动保存重要状态的复杂节点,例如:
2.Chrome 133 支持新的 moveBefore() API
Chrome 133 中推出了新的 moveBefore() DOM API,允许开发者更轻松地在 DOM 中移动元素而不会丢失状态。
Introduces a state-preserving atomic move primitive to the DOM, by calling Node.moveBefore. See https://github.com/whatwg/dom/issues/1255. – Mac, Windows, Linux, ChromeOS, Android
moveBefore() 采用与 insertBefore() 相同的参数,该 API 以原子方式将目标节点移动到新父节点而不重置元素状态,从而允许开发人员能够使用可移动动画 (movable animations)、iframe、全屏元素等构建动态体验。
chrome://flags/#atomic-move
moveBefore() 功能可用于 ParentNode,如 Element、Document、DocumentFragment。其会移动而非移除 / 插入元素,同时保留以下状态:
- iframes 保持加载状态
- 活动 (active) 元素保持焦点
- 弹出窗口、全屏、模式对话框保持打开
- CSS 过渡和动画继续
3.React 支持最新的 moveBefore() API
React 在最新的一个 PR([Fiber] Support moveBefore at the top level of a container #32036)中已经宣布支持 moveBefore 方法,其被用于在保留元素状态的情况下重新对根元素排序。
下面是 React 源码中对 insertInContainerBefore 中的重新实现,其也用到的 moveBefore 方法,同时添加了很多兼容代码:
引入 moveBefore 方法后,对 React 源码中部分文件的体积也产生了一定的影响,但变化不明显,如下图:
参考资料
https://developer.chrome.com/blog/movebefore-api
https://chromestatus.com/feature/5135990159835136
https://github.com/sebmarkbage/react/blob/refs/heads/movebefore/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
https://x.com/htmx_org/status/1887329202573353431