在實務專案開發中,深入理解 dangerouslySetInnerHTML 用法與相關技巧,當資料來源為帶有標籤的 HTML 格式時,React 預設會將所有標籤自動跳脫(Escape)並以純文字字串直接顯示在畫面上,導致頁面出現一堆使用者看不懂的標籤語法。若要在 React 中正確渲染 HTML 內容,核心關鍵在於理解其底層機制與資安防護邊界。
以下將完整解析 dangerouslySetInnerHTML 的正確語法、XSS 資安風險,以及如何搭配 DOMPurify 套件進行安全的 HTML 資料淨化。
dangerouslySetInnerHTML 基本語法與運作機制
在 React 中,dangerouslySetInnerHTML 是框架用來取代原生 DOM 操作中 innerHTML 的屬性。之所以命名帶有 dangerously(危險地),是因為直接將未經處理的字串作為 HTML 渲染,極容易遭受跨網站腳本攻擊(XSS, Cross-Site Scripting)。
1. 帶入變數的標準寫法
若要將遠端 API 抓取的 HTML 字串渲染到畫面上,需透過物件格式傳遞 __html 屬性:
import React from 'react';
function HtmlRenderer({ htmlData }) {
return (
<div dangerouslySetInnerHTML={{ __html: htmlData }} />
);
}
export default HtmlRenderer;
2. 直接寫入 HTML 字串的寫法
也可以不透過變數,直接將包含 HTML 標籤的字串帶入屬性中:
import React from 'react';
function DirectHtmlRender() {
return (
<div dangerouslySetInnerHTML={{ __html: '<p>React HTML tag example</p>' }} />
);
}
export default DirectHtmlRender;
使用 dangerouslySetInnerHTML 的 3 大核心風險與對照
直接使用 dangerouslySetInnerHTML 時,開發者必須承擔與原生 JavaScript 相同的資安與效能風險。以下為常見的考量維度對照:
| 比較維度 | dangerouslySetInnerHTML (未處理) | 搭配 DOMPurify 淨化 | 一般 JSX 預設渲染 |
|---|---|---|---|
| XSS 攻擊風險 | 高(會直接執行惡意 <script> 或 onerror) | 低(會自動過濾危險標籤與屬性) | 零(React 會自動跳脫字串) |
| HTML 支援度 | 支援完整的標籤與結構 | 支援安全的標籤(可自訂白名單) | 僅支援純文字,標籤會被轉譯 |
| 效能與記憶體 | 若頻繁重繪可能影響 DOM 效能 | 需額外進行字串解析運算 | React Virtual DOM 最佳化處理 |
搭配 DOMPurify 套件淨化 HTML 避免 XSS 攻擊
為了防止惡意使用者透過表單輸入或遭受入侵的 API 注入惡意 JavaScript 腳本(例如 <script>alert('XSS')</script> 或 <img src=x onerror=alert(1)>),在將字串交給 dangerouslySetInnerHTML 之前,必須使用業界標準的淨化套件 DOMPurify。
1. 安裝 DOMPurify 套件
請在終端機執行以下指令安裝套件與對應的型別定義(若使用 TypeScript):
npm install dompurify
npm install --save-dev @types/dompurify
2. 生產環境安全渲染範例
以下為整合 DOMPurify 並透過 config 設定限制允許標籤與屬性的標準防禦寫法:
import React from 'react';
import DOMPurify from 'dompurify';
function SafeHtmlRenderer({ rawHtmlData }) {
// 可透過 config 限制僅允許安全的標籤與屬性,進一步提高安全性
const cleanHtmlString = DOMPurify.sanitize(rawHtmlData, {
ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'rel']
});
return (
<div
dangerouslySetInnerHTML={{ __html: cleanHtmlString }}
/>
);
}
export default SafeHtmlRenderer;
開發者高頻 Q&A
Q1:為什麼 React 不像原生 JS 一樣直接提供 innerHTML 屬性?
解法:React 採用宣告式渲染與 Virtual DOM 機制,直接操作 DOM 的 innerHTML 會繞過 React 的 diff 演算法,導致 DOM 節點與元件狀態不同步。而故意取名為 dangerouslySetInnerHTML 則是為了提醒開發者此寫法具備 XSS 風險,應當謹慎使用。
Q2:使用 dangerouslySetInnerHTML 後,內部的 CSS 樣式失效怎麼辦?
解法:該屬性僅負責將 HTML 字串轉為真實 DOM 節點。如果內部的 HTML 包含類別名稱(class="..."),必須確保對應的 CSS 樣式表有載入該樣式;若專案採用 CSS Modules,則需注意全域樣式與區域樣式的覆蓋範圍。
Q3:是否可以只淨化部分信任的標籤?
解法:可以。DOMPurify 支援自訂設定(Config),允許開發者透過參數指定允許保留的標籤(Allowed Tags)與屬性(Allowed Attributes),藉此嚴格控管渲染內容。
總結
在 React 中處理 HTML 字串渲染時,dangerouslySetInnerHTML={{ __html: ... }} 是唯一的直接解法,但絕不能直接將未過濾的使用者輸入或遠端 API 資料丟入其中。務必搭配 DOMPurify 進行內容淨化,才能在兼顧動態內容顯示需求的同時,徹底封鎖潛在的 XSS 攻擊漏洞。
相關參考資料
如果想進一步了解 React 中 dangerouslySetInnerHTML 的詳細用法與說明,可以前往 React 官方文件 – dangerouslySetInnerHTML 進行查看。
發表迴響