避免的常见模式:破坏 React
我绝不是一个专家级的 React 工程师,但我有几年的经验。React 是一个用于构建用户界面的强大库,但在某些地方也很脆弱。我遇到过的一个常见 bug 是由于直接操作 DOM 结合 React而引起的。这是一种反模式,因为在适当的情况下,它可能会破坏整个 React 应用程序,并且很难进行调试。
在我们深入解释问题及其解决方法之前,这里有一个最小示例可以重现此 bug:
const destroyElement = () =>
document.getElementById('app').removeChild(document.getElementById('my-div'));
const App = () => {
const [elementShown, updateElement] = React.useState(true);
return (
<div id='app'>
<button onClick={() => destroyElement()}>
通过 querySelector 删除元素
</button>
<button onClick={() => updateElement(!elementShown)}>
更新元素和状态
</button>
{ elementShown ? <div id="my-div">我是元素</div> : null }
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
这是一个相当简单的 React 应用程序,有一个容器、两个按钮和一个状态变量。问题在于,如果你点击调用 destroyElement()
的按钮,然后再点击另一个按钮,应用程序将崩溃。你可能会问,为什么会这样?问题可能并不明显,但如果你查看浏览器控制台,你会注意到以下异常信息:
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
这可能仍然有些晦涩,所以让我解释一下发生了什么。React 使用自己的 DOM 表示形式,称为虚拟 DOM,以确定要渲染的内容。通常,虚拟 DOM 会与当前的 DOM 结构匹配,React 会处理属性和状态的变化。然后,它会更新虚拟 DOM,并批量发送必要的更改到真实的 DOM。
然而,在这种情况下,React的虚拟DOM和真实DOM是不同的,因为destroyElement()
移除了#my-div
元素。因此,当React尝试使用虚拟DOM的更改更新真实DOM时,由于元素不存在,无法删除该元素。这导致上述异常被抛出,你的应用程序出现错误。
你可以将destroyElement()
重构为App
组件的一部分,并与其状态进行交互,以修复此示例中的问题。无论问题或解决方案有多简单,它都展示了在某些情况下React是多么脆弱。在一个大型代码库中,每天有许多开发人员在不同的领域贡献代码,这种情况只会更加复杂。在这样的环境中,这样的问题很容易引入,追踪它们可能会非常棘手。这就是为什么我建议在与React结合使用时要非常小心直接操作DOM。