1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReactでHTMLタグを表示する方法【dangerouslySetInnerHTML】

Posted at

はじめに

今回、名刺アプリの自己紹介欄で「HTMLタグを含む文字列をそのまま表示したい」という要件に取り組みました。

例えば、データベースに保存されている文字列が以下のような場合:

<h1>僕の名前はJohnです</h1>
<p style="color: red;">赤色のテキストです。</p>

これを普通にReactで表示すると、HTMLタグがエスケープされて文字列としてそのまま表示されてしまいます。でも今回は、HTMLとして解釈して、見出しとして大きく表示したり、赤色で表示したりしたいというのが目的でした。

注意点

セキュリティリスクについて

dangerouslySetInnerHTMLという名前の通り、これは危険な機能です。

なぜ危険なのか?

  • ユーザーが入力したHTMLをそのまま表示すると、XSS(クロスサイトスクリプティング)攻撃のリスクがある
  • 悪意のあるJavaScriptコードが埋め込まれる可能性
<!-- 悪意のある簡単な例 -->
<script>alert('あなたのクッキーを盗みます');</script>

安全に使うためには:

  • 信頼できるソースからのHTMLのみを使用する
  • ユーザー入力をそのまま使う場合は、必ずサニタイズ(無害化)処理を行う
  • 可能であれば、DOMPurifyなどのライブラリを使う

今回のケースでは、個人学習用の小さいアプリで外部公開しないものなので、サニタイズ処理は省略しています。実際の本番環境や外部公開するアプリでは、必ずサニタイズ処理を入れましょう。

dangerouslySetInnerHTMLの基本的な使い方

通常の方法(HTMLがエスケープされる)

// HTMLタグを含む文字列
const htmlString = "<h1>僕の名前はJohnです</h1>";

// これだとHTMLタグが文字列として表示される
<div>{htmlString}</div>

表示結果
image.png

dangerouslySetInnerHTMLを使う方法

// HTMLタグを含む文字列
const htmlString = "<h1>僕の名前はJohnです</h1>";

// HTMLとして解釈される
<div dangerouslySetInnerHTML={{ __html: htmlString }} />

表示結果
image.png

なぜ__htmlというプロパティ名?

アンダースコア2つ(__)で始まる名前は、「これは危険な機能ですよ!使う前によく考えてね!」という警告の意味が込められています。開発者が意図的にこの名前を書かないといけないようにすることで、無意識に危険な機能を使ってしまうことを防いでいます。

Chakra UIと組み合わせる場合の注意点

今回はChakra UIを使っていたので、少し工夫が必要でした。

問題:Chakra UIのスタイルが優先されてしまう

最初はTextコンポーネントを使っていました:

<Text
  fontSize={{ base: "md" }}
  fontWeight="medium"
  color="fg"
  dangerouslySetInnerHTML={{ __html: user.description }}
/>

しかし、これだとHTMLの<h1>タグを埋め込んでも、Chakra UIのTextコンポーネントのスタイル(fontSize: mdなど)が優先されてしまい、見出しとして表示されませんでした。

解決策:Boxコンポーネント + cssプロップ

<Box
  bg="bg.muted"
  p={{ base: 2, sm: 3 }}
  borderRadius="md"
  fontSize={{ base: "md" }}
  lineHeight="1.6"
  wordBreak="break-word"
  css={{
    "& *": {
      all: "revert",
      color: "inherit",
    },
    "& h1, & h2, & h3, & h4, & h5, & h6": {
      fontWeight: "bold",
      marginTop: "0.5em",
      marginBottom: "0.5em",
    },
    "& h1": { fontSize: "2em" },
    "& h2": { fontSize: "1.5em" },
    "& h3": { fontSize: "1.17em" },
    "& p": {
      marginTop: "0.5em",
      marginBottom: "0.5em",
    },
  }}
  dangerouslySetInnerHTML={{ __html: user.description }}
/>

各CSSプロパティの意味

all: "revert"

すべてのスタイルをブラウザのデフォルトに戻します。Chakra UIが適用したスタイルをリセットして、HTMLタグ本来の見た目を取り戻すための設定です。

color: "inherit"

親要素の色を継承します。これにより、ライトモード・ダークモードの切り替えに対応できます。

"& h1, & h2, & h3, & h4, & h5, & h6"

&は「この要素の中にある」という意味です。つまり、& h1は「このBox要素の中にあるh1要素」を指します。

見出しタグに共通のスタイル(太字、上下のマージン)を適用しています。

fontSize: "2em"

相対的なフォントサイズです。2emは親要素のフォントサイズの2倍という意味で、これによりh1が大きく表示されます。

実際にテストしてみた結果

以下のようなテスト用HTML文字列を作成して確認しました:

const testDescription = `
  <h1>見出し1のテスト</h1>
  <h2>見出し2のテスト</h2>
  <p>通常のパラグラフです。</p>
  <p style="color: red;">赤色のテキストです。</p>
  <p style="color: blue; font-weight: bold;">青色で太字のテキストです。</p>
  <p style="color: green; font-size: 20px;">緑色で大きいテキストです。</p>
  <p><span style="background-color: yellow;">黄色の背景</span>のテキストです。</p>
  <h3 style="color: purple;">紫色の見出し3</h3>
  <p style="font-style: italic; color: orange;">オレンジ色のイタリック体です。</p>
`;

image.png

結果、すべてのスタイルが正しく適用されました!

  • 見出しがそれぞれ異なるサイズで表示される
  • 色(red, blue, green, purple, orange)が正しく反映される
  • 太字、イタリック体も適用される
  • 背景色(yellow)も表示される

まとめ

  • dangerouslySetInnerHTMLを使うとReactでHTMLを動的に埋め込める
  • 二重の波括弧{{ __html: content }}の構文を使う
  • セキュリティリスクがあるので、信頼できるデータのみに使う
  • Chakra UIなどのUIライブラリと組み合わせる場合は、CSSで調整が必要
  • all: "revert"でスタイルをリセットできる

JISOUのメンバー募集中!

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?