React中何时使用dangerouslySetInnerHTML?相关疑问解答
关于React中
dangerouslySetInnerHTML的解析与替代方案 一、为什么它叫“dangerous”?
React默认会自动转义所有插入到JSX里的文本——比如你写<div>{'<script>恶意代码</script>'}</div>,React会把尖括号转成实体字符,最终页面只会显示这段文本,不会执行脚本。但dangerouslySetInnerHTML会跳过这个转义流程,直接把传入的HTML字符串渲染成真实DOM节点。
如果这段HTML来自不可信来源(比如用户输入、未过滤的第三方接口数据),攻击者可以注入恶意脚本,窃取用户Cookie、篡改页面内容甚至发起钓鱼攻击。名字里的“dangerous”就是React团队给开发者的强提醒:这个属性有XSS风险,必须谨慎使用。
二、什么时候该用它?
只有在你能100%确保要渲染的HTML内容是安全可信的情况下才用:
- 后端返回的、经过严格XSS过滤/清洗的富文本(比如后台编辑器生成的内容,已经剔除了所有恶意标签和脚本)
- 你自己硬编码的固定HTML片段,完全没有外部输入的部分
三、工作原理
它是React提供的特殊属性,必须接收一个包含__html键的对象,值就是要渲染的HTML字符串。React会直接把这段HTML解析并插入到对应DOM节点中,跳过常规的JSX转义逻辑。示例:
function TrustedContent() { // 假设这是后端返回的已过滤富文本 const safeHtml = "<p>这是经过安全处理的<b>加粗文本</b></p>"; return <div dangerouslySetInnerHTML={{ __html: safeHtml }} />; }
四、针对你的问答网站的替代方案
如果你的问题卡片内容是结构化的(比如题目里的加粗、列表、公式等),完全可以不用dangerouslySetInnerHTML,推荐这几种更安全的方式:
1. 用JSX直接构建结构化内容
如果题目格式固定,把内容拆成JSX元素来渲染,完全规避HTML注入风险:
function QuestionCard({ question }) { // 假设question是包含结构化字段的对象 return ( <div className="question-card"> <h3>{question.title}</h3> <p> {question.content.normalText} {question.content.highlightText && <b>{question.content.highlightText}</b>} </p> <ul> {question.options.map(opt => ( <li key={opt.id}>{opt.text}</li> ))} </ul> </div> ); }
2. 使用富文本解析库
如果需要支持复杂格式(比如Markdown、轻量富文本),用专门的库把格式文本转成安全的JSX:
- 若内容是Markdown格式,用
react-markdown直接渲染:
import ReactMarkdown from 'react-markdown'; function QuestionCard({ questionMarkdown }) { return ( <div className="question-card"> <ReactMarkdown>{questionMarkdown}</ReactMarkdown> </div> ); }
- 若必须处理HTML,先用
sanitize-html过滤恶意内容,再结合dangerouslySetInnerHTML使用:
import sanitizeHtml from 'sanitize-html'; function QuestionCard({ rawHtml }) { const cleanHtml = sanitizeHtml(rawHtml, { allowedTags: ['b', 'i', 'p', 'ul', 'li'], // 只允许指定安全标签 allowedAttributes: {} // 禁止所有属性,避免脚本注入 }); return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />; }
3. 封装自定义格式组件
把常用的格式(加粗、引用、代码块)封装成React组件,通过结构化数据驱动渲染:
function Bold({ children }) { return <b>{children}</b>; } function QuestionCard({ questionSegments }) { return ( <div className="question-card"> {questionSegments.map((seg, idx) => { switch(seg.type) { case 'text': return <span key={idx}>{seg.content}</span>; case 'bold': return <Bold key={idx}>{seg.content}</Bold>; case 'list': return ( <ul key={idx}> {seg.items.map((item, i) => <li key={i}>{item}</li>)} </ul> ); default: return null; } })} </div> ); }
内容的提问来源于stack exchange,提问作者Gire Munga
相关产品推荐
相关产品推荐

