紀錄工作經驗、相關知識,解決技術相關問題。

Javascript, React

React 如何正確渲染 HTML 字串?dangerouslySetInnerHTML 用法與範例

React 如何正確渲染 HTML 字串?dangerouslySetInnerHTML 用法與範例 特色圖片

在實務專案開發中,深入理解 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 進行查看。

發表迴響