電通総研 テックブログ

電通総研が運営する技術ブログ

【React】LinkifyとMUIを一緒に使って文字列内のURLをリンク化する

こんにちは、X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの大西です。現在、TypeScriptとReactを使ってWebアプリを開発していますが、フロントエンドの実装を任された際にLinkifyMUI(Material UI)を合わせて使う場面がありました。LinkifyとMUIのコラボ実装はあまり記事がなかったので記事を書いてみました。

Linkifyとは?

Reactでテキスト内のURLをリンクにしたい場面があったとします。こんな感じですね。
テキスト内のURLをリンクにする

Linkifyとは、文字列にURLが含まれているときに自動でリンク化する際に使えるライブラリです。
このように自動でリンク化する方法を探すと、Linkifyを入れて3つほど方法がありました。

  1. Linkifyを使う
  2. react-string-replaceを使う
  3. 自前で実装する

2のreact-string-replaceは1のLinkifyに比べGitHubのFork数やstar数が少なく、3の自前実装は、dangerouslySetInnerHTMLという関数を使わなければいけなかったため今回はLinkifyを使うことにしました。Linkifyはドキュメントもしっかり整備されていて、XSS(クロスサイトスクリプティング)攻撃に対して脆弱にならないような方法も示されていたのでとても良かったです。

MUI(Material UI)とは?

MUIはReactアプリケーションを構築するのに使えるUIコンポーネントの巨大なライブラリです。基礎的なUI要素から高度なUI要素までそろっており、カスタマイズ可能なライブラリが備わっています。基本的に無料で利用できますが、一部有料のライブラリもあります。MUIを使うと、例えば表(テーブル)プログレスバーにしても、自分で一から実装するより簡単に実現できます。
MUIを使用して作った表↓
MUIを使用して作った表
MUIを使用して作ったプログレスバー
MUIを使用して作ったプログレスバー

LinkifyとMUIを一緒に使う

Reactアプリを開発している中で、LinkifyとMUIを同時に使いたい場面が出てきました。例えばMUIのLinkコンポーネントとLinkifyを同時に使いたい場合です。LinkifyはもちろんLinkコンポーネントを使わなくても以下のように書くと文字列をリンク化できます。

<Linkify>{props.content}</Linkify>

しかし、チーム内であらかじめ統一して作っておいたカスタムリンクコンポーネント(今回は「CustomLink」というカスタムリンクコンポーネントがあったとします)を使いたい場合は少し工夫が必要で、renderオプションが必要です。renderオプションを用いると、リンク要素の生成方法をオーバーライドできます。ターゲットリンクの中間表現(IntermediateRepresentation。タグ名、属性、テキストコンテンツ等を含むオブジェクト)を受け入れる関数を指定し、戻り値は、文字列、HTML 要素、またはインターフェイス固有のエンティティにします。render関数は、結果 (属性、コンテンツなど)を引数として他のすべてのオプションが計算された後に実行されます。実装すると以下のようになります。

チーム内で統一して定義したカスタムリンクコンポーネント

import { Link, LinkProps } from "@mui/material";

export const CustomLink = (props: LinkProps): JSX.Element => {
  const { href, children, ...other } = props;

  return (
    <Link {...other} href={href} target="_blank" rel="noopener noreferrer" color="#FF0000"...などチーム内で設定したオプション>
      {children}
    </Link>
  );
};

上記のカスタムリンクコンポーネントを使えるように新しいコンポーネントを作成する↓

import { IntermediateRepresentation } from "linkifyjs";
import { CustomLink } from "CustomLinkを定義したファイル";

export const renderLink = (ir: IntermediateRepresentation): JSX.Element => {
  return <CustomLink {...ir.attributes}>{ir.content}</CustomLink>;
};

そして、CustomLinkをリンク化したいコンテンツを表示するtsxファイルに持っていき、renderオプションに渡す↓

<Linkify options={{ render: renderLink }}>{props.content}</Linkify>

としてやれば、チーム内で統一して使っているカスタムリンクコンポーネントを使って、文字列内のURLをリンク化できます!IntermediateRepresentationについて補足ですが、
・ir.attributes 内に href プロパティなどaタグの属性が渡ってくる
・ir.content はリンクの表記が渡ってくる
という仕様になっています。

ちなみにLinkコンポーネントを扱う際にtarget="_blank"をつける場合は、rel="noopener"rel="noreferrer"オプションをつけてあげると安心です。target="_blank"をつけるとリンクをクリックしたときに別タブでリンク先のページが開きますが、開いた先のページが悪意ある者によって作成されたページであった場合、リンク元のページを操作される危険があります。しかしrel="noopener noreferrer"をつけてあげることで、リンク元のページが操作されるのを防いだり、リンク元の情報を渡さないようにできます。

まとめ

このように、LinkifyとMUIを一緒に使うことで、チーム内で統一して使っているカスタムリンクコンポーネントを使って文字列内のURLをリンク化できます。LinkifyはURLだけでなく、メールアドレスもリンク化できますし、プラグインを使用すればTwitterハッシュタグやメンション、IPアドレス、また自分が指定したキーワードも検出してリンク化できてしまいます!便利なオプションもそろっているので、ぜひ使ってみてください!

私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。

セキュリティエンジニア

執筆:@onishi.mayu、レビュー:@kano.nanami
Shodoで執筆されました