【React】linkifyjsを使って文字列中のURLをリンクに変換+メンション部分も良い感じにcssをあてる

こんにちは、[Yui](https://twitter.com/yui_active)です。

今回は文字列のURLをリンクに変換して、メンションされた部分は独自にcssを当てて、わかりやすいスタイルにしたいと思います。

↓こんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670446/3d86b4d4-7e2a-32ed-f6ab-f45f680737ca.png)
codesandboxのリンクは以下（codesandbox上ではTS入っていません＆若干記事中のコードと異なる部分があります。）
https://codesandbox.io/s/linkify-forked-z5eht?file=/src/index.js:488-493


## どのパッケージを使うか
ハイパーリンクを設定する系のパッケージは非常に沢山あり、軽く調べただけでも[react-linkify](https://github.com/tasti/react-linkify)や、[linkify-it](https://github.com/markdown-it/linkify-it)などあって迷ったのですが、今回は[linkifyjs](https://github.com/Soapbox/linkifyjs)を使うことにしました。

理由は以下です。

- 更新日が最近だった
- 作成されたのが古かった（長く使われてる）
- プラグインが充実していてカスタマイズしやすかった

上記のパッケージはどれもそこまで古いものでもなく、現在もきちんとメンテナンスされているような感じでしたが、linkifyjsがその中でも更新頻度が高かったので、大きなバグなどはなさそうだなと感じました。
また、今回必要なのが、上記のパッケージだったらデフォルトでついているURLの他には、メンション機能だけなので、プラグインで簡単に設定できるのは楽だなと感じました。

※当初更新頻度や知名度の高さから[autolinker](https://github.com/gregjacobs/Autolinker.js)を使おうとしたのですが、mention機能が、公式のREADME.mdにある通り↓、twitterタイプかinstagramタイプか、soundcloudタイプかしかなく、わざわざメンション部分を自作することもしたくないなと思ったのでやめました。（カスタマイズはもちろんできます。）

>mention : string
A string for the service name to have mentions (@username) auto-linked to. Supported values at this time are 'twitter', 'soundcloud' and 'instagram'. Pass false to skip auto-linking of mentions. Defaults to false.

メンションがリンクになってしまうのは仕方がないにしても、``@hoge``で自動的に``twitterの@hogeさん``あてのリンクを作成してしまうのは今回のパターンでいうとあまり好ましくないので...。逆に``@hoge``でTwitterやinstagramとかのアカウントに飛ばしたい場合はかなり使いやすいと思います。

というわけで、前置きが長くなりましたが、今回はlinkifyjsを使うことにしました。

## インストール

```
$ npm install linkifyjs
or 
$ yarn add linkifyjs
```

私はTypeScriptを導入してたので、型もインストールしました。

```
$ yarn add @types/linkifyjs
```

これだけで各種プラグインも使えるようになります。``linkifyjs/plugins/mention``や``linkifyjs/plugins/hashtag``などがあってめちゃくちゃ便利。
電話番号、メールアドレスも認識できるものもあります。

## 利用

```react:Index.tsx

//まずはパッケージをインストール
import Linkify from 'linkifyjs/react'
import * as linkify from 'linkifyjs'
import mention from 'linkifyjs/plugins/mention'

const Index = () => {
  // Linkifyの設定..URLとメンションは独自にcssをあてる
  mention(linkify)
  const linkifyOptions = {
    className: function (_href: string, type: string) {
      if (type === 'mention') {
        return styles.mention
      }
      return styles.link
    },
    target: {
      url: '_blank',
    },
  }
  return (
  // 省略
    <div>
      <Linkify options={linkifyOptions}>
        {text}
      </Linkify>
    </div>
  )
}
```

リンクを認識させたい文字列を``<Linkify>``で囲えばOKです。
今回cssをurlとmentionで別に当てたかったので、optionsで指定しています。
ちなみに別タブで開くという``url: '_blank',``は、設定しなくてもデフォルトの設定上そうなっているのですが、今回optionを別でつけたからか、この記載がないと別タブで開いてくれなかったので、ここで記載しました。

## メンションの場合はクリックできないようにする（おまけ）

今回、linkifyを使った関係で、``@hoge``で``<a>``タグが生成されることになってしましいました。
ただ、今回のアプリでは個別ページを作成しているわけではなかったので、``/hoge``あてに飛んでも何もない虚無のページに行くだけです。
それは都合が悪いので、cssでクリック時のイベントを禁止しておきます。

```scss
  .link {
    text-decoration: underline;
  }
  .mention {
    pointer-events: none;
  }
```

↑リンククラスはアンダーラインをつけて、メンションクラスではイベントをnoneにする。

もちろんこれは簡単に改ざんできますが、改ざんされても``/hoge``で機密情報が見えるわけでもないので、問題ないかと思います。
厳密にやるなら``<a>``タグを``<span>``や``<div>``タグに書き換えて、という処理を加えたほうが良いのかもしれませんが.....今回はそれを行うメリットが見当たらなかったのでこれでよしとします。

あとは適当に好みで色を変えるなり、背景色を設定するなりすればいい感じになります。

ざっくりと書いたコードはこちら。
https://codesandbox.io/s/linkify-forked-z5eht?file=/src/index.js:488-493