React の dangerouslySetInnerHTML とは何か?

TwitterFacebookHatena

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;')
}

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 を使う上で、この属性を使う機会は少なくないかもしれません。ただ、その際には常に安全性を考えて、注意深く使うようにしましょう。

React の dangerouslySetInnerHTML とは何か?