说说你对 dangerouslySetInnerHTML 的理解
本文介绍了在React应用程序中使用dangerouslySetInnerHTML
属性的原因,它相当于浏览器DOM中的innerHTML
属性。
什么是dangerouslySetInnerHTML
?
dangerouslySetInnerHTML
是一个属性,你可以在 React 应用程序中的 HTML 元素上使用,以编程方式设置其内容。你可以直接在元素上使用这个属性,而不是使用选择器来抓取HTML元素,然后设置其innerHTML
。
当使用dangerouslySetInnerHTML
,React也知道该特定元素的内容是动态的,对于该节点的子节点,它只是跳过与虚拟DOM的比较,以获得一些额外的性能。
正如该属性的名称所暗示的,使用它可能是危险的,因为它使你的代码容易受到跨站脚本(XSS)攻击。特别是当你从第三方来源获取数据或渲染用户提交的内容时,这就成为一个问题。
何时使用dangerouslySetInnerHTML
你需要设置DOM元素的HTML内容的一个用例是当你用来自富文本编辑器的数据填充一个<div>
。想象一下,你有一个网页,人们可以提交评论,你允许他们使用一个富文本编辑器。在这种情况下,富文本编辑器的输出很可能是带有标签的HTML,如<p>
,<b>
, 和<img>
。
考虑一下下面的代码片段,它将在不知道其中的<b>
标签的情况下渲染字符串--意味着输出的只是字符串本身,没有任何粗体字,就像这样:lorem ipsum。
const App = () => {
const data = 'lorem <b>ipsum</b>';
return (
<div>
{data}
</div>
);
}
export default App;
但当使用dangerouslySetInnerHTML
,React就会意识到HTML标签,并正确渲染它们。这一次,输出将以粗体文本正确呈现(即loremipsum)。
const App = () => {
const data = 'lorem <b>ipsum</b>';
return (
<div
dangerouslySetInnerHTML={{__html: data}}
/>
);
}
export default App;
请注意,它应该是一个带有传递给__html
键的对象dangerouslySetInnerHTML
。除此之外,你使用dangerouslySetInnerHTML
属性的元素不应该有任何孩子,因此要使用<div>
元素作为自闭标签。
传递对象的要求只是另一种保障措施,以防止开发者在没有阅读文档和意识到潜在危险的情况下使用它。
使用时的消毒dangerouslySetInnerHTML
上面的例子在渲染时不会造成危险。然而,在某些情况下,HTML元素可能会执行一个脚本。
考虑一下下面的例子,一个JavaScript事件被附加到一个HTML元素上。虽然这些是无害的例子,但它们是概念的证明,表明一个HTML元素如何被利用来运行恶意脚本。
const App = () => {
const data = `lorem <b onmouseover="alert('mouseover');">ipsum</b>`;
return (
<div
dangerouslySetInnerHTML={{__html: data}}
/>
);
}
export default App;
const App = () => {
const data = `lorem ipsum <img src="" onerror="alert('message');" />`;
return (
<div
dangerouslySetInnerHTML={{__html: data}}
/>
);
}
export default App;
幸运的是,有针对HTML的净化工具,可以检测出HTML代码中潜在的恶意部分,然后输出一个干净安全的版本。最受欢迎的HTML净化工具是DOMPurify。
让我们使用它的在线演示来对上述HTML代码进行消毒,看看它是如何检测并过滤掉代码中可能在执行时产生危险的部分的。
Original
lorem <b onmouseover="alert('mouseover');">ipsum</b>
Sanitized
lorem <b>ipsum</b>
Original
lorem ipsum <img src="" onerror="alert('message');" />
Sanitized
lorem ipsum <img src="">
即使在我们信任数据来源的情况下,使用消毒剂也是很好的做法。在使用DOMPurify包的情况下,上面的一个例子会是这样的。
import DOMPurify from 'dompurify'
const App = () => {
const data = `lorem <b onmouseover="alert('mouseover');">ipsum</b>`
const sanitizedData = () => ({
__html: DOMPurify.sanitize(data)
})
return (
<div
dangerouslySetInnerHTML={sanitizedData()}
/>
);
}
export default App;
sanitizedData
函数返回一个带有__html
键的对象,它有一个从DOMPurify.sanitize
函数返回的值。
正如预期的那样,当我们将鼠标悬停在粗体字上时,并没有执行警报函数。
请注意,由于DOMPurify需要一个DOM树,而Node环境没有,你要么使用jsdom
包来创建一个window
对象,并用它来初始化DOMPurify
,要么单独使用isomorphic-dompurify
包来代替,它同时封装了DOMPurify
和jsdom
包。
如果你喜欢第一种选择,你可以参考以下来自DOMPurify
的文档片段。
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const clean = DOMPurify.sanitize(dirty);
结论
总之,dangerouslySetInnerHTML
只不过是React中innerHTML
的替代品,应该谨慎使用。虽然这个名字暗示了使用它的危险性,但通过使用一个完善的净化器采取必要的措施,确保代码是干净的,在React节点内呈现时不会运行意外的脚本。