【问题标题】:How to put back HTMLElement into React component after the was (re)moved without React如何在没有 React 的情况下(重新)移动后将 HTMLElement 放回 React 组件
【发布时间】:2021-11-14 12:53:21
【问题描述】:

我们正在用 React 组件包装一个组件库,但在某些情况下,该库以这样一种方式操作 DOM 树,使得 React 在尝试删除 React 组件时会崩溃。

这是重现问题的示例:

function Sample () 
{
  let [shouldRender, setShouldRender] = React.useState(true);
  return (
    <React.Fragment>
      <button onClick={() => setShouldRender(!shouldRender)}>show/hide</button>
      { shouldRender && <Component /> }
    </React.Fragment>
  );
}

function Component () 
{
  let ref = React.useRef();
  React.useEffect(() => {
    let divElement = ref.current;
    someExternalLibrary.setup(divElement);
    return () => someExternalLibrary.cleanup(divElement);
  });
  return <div ref={ref} id="div1">Hello world</div>;
}

ReactDOM.render(
  <Sample />,
  document.getElementById('container')
);

let someExternalLibrary = {
  setup: function(divElement)
  {
    let beacon = document.createElement('div');
    beacon.id = `beacon${divElement.id}`;
    divElement.parentElement.replaceChild(beacon, divElement);
    
    document.body.append(divElement);
  },
  cleanup: function(divElement)
  {
    let beacon = document.getElementById(`beacon${divElement.id}`);
    beacon.parentElement.replaceChild(divElement, beacon);
  }
}

你可以找到this sample on JSFiddle

上面的示例将呈现与someExternalLibrary 集成的Component。 外部库将元素从 React 组件内部移动到其他地方。 即使外部库使用信标将元素放回其原始位置,当您单击显示/隐藏按钮时,React 仍会在尝试删除组件时报错。

这将是错误

"Error: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
    at removeChildFromContainer (https://unpkg.com/react-dom@17/umd/react-dom.development.js:10337:17)
    at unmountHostComponents (https://unpkg.com/react-dom@17/umd/react-dom.development.js:21324:11)
    at commitDeletion (https://unpkg.com/react-dom@17/umd/react-dom.development.js:21377:7)
    at commitMutationEffects (https://unpkg.com/react-dom@17/umd/react-dom.development.js:23437:13)
    at HTMLUnknownElement.callCallback (https://unpkg.com/react-dom@17/umd/react-dom.development.js:3942:16)
    at Object.invokeGuardedCallbackDev (https://unpkg.com/react-dom@17/umd/react-dom.development.js:3991:18)
    at invokeGuardedCallback (https://unpkg.com/react-dom@17/umd/react-dom.development.js:4053:33)
    at commitRootImpl (https://unpkg.com/react-dom@17/umd/react-dom.development.js:23151:11)
    at unstable_runWithPriority (https://unpkg.com/react@17/umd/react.development.js:2764:14)
    at runWithPriority$1 (https://unpkg.com/react-dom@17/umd/react-dom.development.js:11306:12)"

一个简单的解决方法是将现有的 HTML 包装在另一个 DIV 元素中,以便成为组件的根,但不幸的是,这在我们的项目中并不总是可行的,所以我需要另一个解决方案。

解决这个问题的最佳方法是什么?
有没有办法在清理期间使用 ReactFragment 并将 HTMLElement 与片段重新关联?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    useEffect 的清理功能,也就是 someExternalLibrary.cleanup(),在 React 更新了 DOM(从 DOM 中删除了 div)之后被调用。它失败了,因为 React 正在尝试删除一个被 someExternalLibrary.setup() 删除的 DOM 节点(并且 someExternalLibrary.cleanup() 稍后会放回去)。

    在类组件中,您可以调用someExternalLibrary.cleanup() 之前 React 更新 DOM。这将修复您的代码:

    class Component extends React.Component { 
        constructor(props) {
            super(props);
            this.divElementRef = React.createRef();
        }
        componentDidMount() {
            someExternalLibrary.setup(this.divElementRef.current);
        }
        componentWillUnmount() {
            someExternalLibrary.cleanup(this.divElementRef.current);
        }
        render() {
            return <div ref={this.divElementRef} id="div1">Hello world</div>;
        }
    }
    

    “额外 div 解决方案”失败的原因相同:在清理 useEffect() 期间调用 someExternalLibrary.cleanup() 将意味着 DOM 已更改,但 someExternalLibrary 预计 DOM 不会更改。通过使用带有 componentWillUnmount 的类组件,someExternalLibrary.setup()someExternalLibrary.cleanup() 将使用相同的 DOM。

    【讨论】:

    • 这很有帮助。谢谢你。我在使用useEffect 时遵循了供应商的指导,但使用useLayoutEffect 和基于类的组件解决了我的示例中的问题。我会尝试将此应用到实际项目中。
    • div 包装解决方案确实适用于我们正在集成的这个组件库中的某些组件,但正如您所指出的,在我的示例中不起作用。我将不得不研究不同行为的来源。
    • 更新了 JSBin 以防它帮助任何人:jsbin.com/jehadox/edit?js,console,output
    【解决方案2】:

    React 不希望您删除 React 创建的节点。将 React 创建的节点视为只读的。相反,您应该:

    将现有的 HTML 包装在另一个 DIV 元素中

    但是你没有解释为什么这是不可行的:

    但不幸的是,这在我们的项目中并不总是可行的,所以我需要另一个解决方案。

    没有这些信息,这是您真正的问题,无法给出解决方案。

    【讨论】:

    • 为了使某些组件正常工作,HTML 结构必须符合组件库的 CSS 和 JS 的预期。在某些情况下,用另一个节点包装会破坏这一点。即使它可以工作,我也想避免创建无意义的节点,但至少我现在可以继续前进。
    • "要使某些组件正常工作,HTML 结构必须符合组件库的 CSS 和 JS 的预期。" => 这并不能解释为什么你必须删除由 React 创建的 div。 --- “即使它可以工作,我也想避免创建无意义的节点” => 这不是没有意义的,否则 React 会崩溃。
    • 如果我将外部库的 CSS 和 JS 包装在 div 中,它将无法按预期工作。我无法为这个组件库实现预期的 HTML 结构。
    猜你喜欢
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    • 2020-12-29
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多