TL;DR
このページでは、React のdangerouslySetInnerHTML
の実装方法について解説しますね。一言でいうとdangerouslySetInnerHTML
とは、React で直接 HTML を描画するための手段です。サードパーティからのコンテンツ表示やリッチテキストエディターなどで使われます。ただし、この名前が示す通り、この属性を使うときは注意が必要です。
以下が、今回使用する開発環境とそのバージョンです。
開発環境 | バージョン |
---|---|
Next.js | 13.4.4 |
TypeScript | 5.0.4 |
Emotion | 11.11.0 |
React | 18.2.0 |
'dangerouslySetInnerHTML' とは?
React では、通常、JSX を使って UI を描画します。しかし、場合によっては直接 HTML を描画したいこともあります。そのときに活用できるのがdangerouslySetInnerHTML
です。
まずは簡単なコードから始めてみましょう。
const Component = () => {
return <div dangerouslySetInnerHTML={{ __html: '<h1>Hello World</h1>' }} />
}
上記のコードは、<h1>Hello World</h1>
という HTML を直接描画します。このときに使っているのが、dangerouslySetInnerHTML
属性です。
ただ、この属性の名前にある'dangerously'のとおり、これを使うときは慎重になる必要があります。なぜなら、この属性を使うと XSS(クロスサイトスクリプティング)というセキュリティリスクが生じるからです。XSS とは、不正なスクリプトが実行されてしまう攻撃手法のことで、これを防ぐためには、描画する HTML のエスケープが重要になります。
どのようなときに使うのか?
dangerouslySetInnerHTML
を使用する場合は、その HTML 文字列が信頼できるソースから来ているか、または適切にサニタイズ(悪意のあるコードの除去)されていることを確認する必要があります。
dangerouslySetInnerHTML
の使用は、基本的には HTML コンテンツを直接描画する必要があるケース全般に適用されます。ただし、このプロパティは XSS 攻撃のリスクがあるため、使用は最小限に抑え、適切なサニタイズ処理が行われていることが前提となります。
サードパーティからのコンテンツ表示
外部 API から取得した HTML 形式のコンテンツ(ニュース記事、ブログ投稿など)を表示するときなどに使われます。この場合、取得したコンテンツが信頼できるソースから提供されていること、またはサニタイズ処理を行って悪意のあるコードを除去していることが必要です。
リッチテキストエディター
リッチテキストエディター(テキストの装飾、リンク、画像の挿入などが可能なエディター)を実装している場合、ユーザーが作成したリッチテキストの内容を HTML として表示する必要があります。そのため、dangerouslySetInnerHTML
を使うことで HTML タグを含むテキストを適切にレンダリングできます。
SVG の挿入
SVG 形式のアイコンや図形を表示する際にもdangerouslySetInnerHTML
を使用することがあります。ただし、SVG もスクリプトを含むことが可能なので、SVG データが信頼できるソースから得られていることを確認する必要があります。
マークダウンを HTML に変換する
マークダウンエディタでユーザーが書いたテキストをウェブページ上でレンダリングしたいとき、そのマークダウンテキストを HTML に変換するライブラリ(例えば、marked や remark など)を使います。この変換された HTML 文字列をdangerouslySetInnerHTML
でレンダリングすることで、ユーザーが書いたマークダウンテキストが HTML として表示されます。
HTML プレビュー
ユーザーが直接 HTML を編集するエディタを提供している場合、その HTML をリアルタイムでプレビュー表示する必要があります。その際にdangerouslySetInnerHTML
を使用して、ユーザーが編集した HTML をそのままウェブページに表示します。
いずれの場合も、ユーザーが入力した内容が悪意のあるコードを含んでいないか確認するために、入力をサニタイズするなどのセキュリティ対策を講じるべきです。
dangerouslySetInnerHTML
を使うために
安全にTypeScript を使って、dangerouslySetInnerHTML
をより安全に使うための方法を見ていきます。まずはエスケープ関数を作成します。
const escapeHtml = (unsafe: string) => {
return unsafe.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
}
const Component = ({ html }: { html: string }) => {
return <div dangerouslySetInnerHTML={{ __html: escapeHtml(html) }} />
}
ここではまず、HTML をエスケープするための関数escapeHtml
を作りました。この関数は、HTML で特殊な意味を持つ文字(&
、<
、>
、"
、'
)を対応する HTML エンティティに置き換えます。これにより、HTML がそのまま描画されるのを防ぐことができます。
次に、このescapeHtml
関数を使ってdangerouslySetInnerHTML
の HTML をエスケープしています。これにより、コンポーネントに渡される HTML がそのまま描画されるのを防ぐことができます。
ヘッドレス CMS との連携
次は、microCMS で、記事のコンテンツを出力するためのコードです。
このソースコードの一部であるdangerouslySetInnerHTML
は、React が提供するエスケープされていない HTML をコンポーネントに挿入するためのプロパティです。名前にdangerously
がついているのは、このプロパティを使用すると、XSS(クロスサイトスクリプティング)のようなセキュリティリスクが生じる可能性があるからです。このため、dangerouslySetInnerHTML
は制御された状況下で、安全に扱う必要があります。
export default function BlogId({ blog }) {
return (
<main>
<h1>{blog.title}</h1>
<p>{blog.publishedAt}</p>
<div
dangerouslySetInnerHTML={{
__html: `${blog.body}`,
}}
/>
</main>
)
}
microCMS というヘッドレス CMS(API ベースの CMS)は、ブログ記事のようなリッチテキスト(フォーマットされたテキストやメディアを含むテキスト)を管理するのに便利です。このようなリッチテキストは、HTML 形式で提供されることが一般的です。
このコードスニペットでは、blog.body
というプロパティがdangerouslySetInnerHTML
に渡されています。これは、blog.body
が HTML 形式であると予想され、そのままレンダリングするためにdangerouslySetInnerHTML
が使用されています。
具体的には次のような構造になります。
<div
dangerouslySetInnerHTML={{
__html: `${blog.body}`,
}}
/>
ここで、dangerouslySetInnerHTML
はオブジェクトを受け取ります。その中の__html
キーは特殊で、これに代入された文字列が HTML として挿入されます。文字列テンプレート${blog.body}
はblog.body
を文字列として挿入します。
この方法で、blog.body
の中身が直接的に HTML として解釈され、描画されます。つまり、もしblog.body
が<p>Hello, world!</p>
であれば、これがそのまま HTML としてページに描画され、ユーザーは"Hello, world!"という段落を見ることができます。
ただし、dangerouslySetInnerHTML
はその名前が示す通り、使用には注意が必要です。不適切に使用すると XSS 攻撃などのセキュリティリスクを生じる可能性があります。なので、挿入する HTML が完全に信頼できる(例えば CMS からの直接の出力など)場合のみ使用すべきであり、ユーザーからの入力をそのまま挿入するような場合には使用すべきではありません。
注意点としての HTML エスケープ
ここまでで、dangerouslySetInnerHTML
を使うための基本的な方法とその注意点を見てきました。しかし、これだけで十分というわけではありません。これからも、さまざまな場面やケースに対応するために、どのような HTML をエスケープするか、どの程度エスケープするかを考える必要があります。
また、エスケープを行うことで安全性は高まりますが、それでもなお完全な安全性を保証するものではありません。可能な限り、dangerouslySetInnerHTML
の使用は避け、他の手段を考えることも大切です。
以上が、React のdangerouslySetInnerHTML
とその使い方についての解説です。React を使う上で、この属性を使う機会は少なくないかもしれません。ただ、その際には常に安全性を考えて、注意深く使うようにしましょう。