joppot

コピペで絶対動く。説明を妥協しない

プログラミング

React WebでURLをaタグのリンクにして表示する

投稿日:


概要

みなさんこんにちはcandleです。今回はReactでコメントなどに投稿されたURLからリンクを生成する方法を紹介します。しかし、URLからリンクの自動生成は結構危険です。無尽蔵にリンク化を許可するとスパムやウイルス配布サイトへのリンクを貼られる可能性があります。また、十分にXSS対策をする必要があります。利用する際には十分に気をつけてください。

今回試すのは文字列をLinkifyを使う場合と自作の関数を使った場合の2通り試してみます。

前提

なし

準備

もしも、サンプルコードを動かしたい場合は下のコードをsrc/App.jsなどに貼り付けて実行してみてください。

import React, { Component } from 'react'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      content: '',
    }
    this.handleFormInputChanged = this.handleFormInputChanged.bind(this)
  }

  handleFormInputChanged(event) {
    this.setState({
      [event.target.name]: event.target.value,
    })
  }

  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <div>サンプル</div>
        <textarea
          name="content"
          value={this.state.content}
          onChange={this.handleFormInputChanged}
          style={{
            width: '400px',
            height: '300px',
            fontSize: '15px',
          }}
        />
        <div style={{ marginTop: '50px' }}>{this.state.content}</div>
      </div>
    )
  }
}

export default App

Linkifyを使って文字列中のURLをリンク化する

文字列の中に含まれているURLをリンク化する方法は大き加えて2つあります。Linkifyライブラリを利用する方法と自分でリンク化する方法です。最初にLinkfiyライブラリを使った方法を紹介します。Linkifyをインストールして

 yarn add react-linkify

Linkifyをインポートします。

import Linkify from 'react-linkify'

もしも上のサンプルコードを利用しているならsrc/App.jsのrender関数内にこのように書けば文字列がリンクに変換されます。要はリンク化したい文字列を<Linkify>コンポーネントで囲えば良いのです。

<div style={{ marginTop: '30px' }}>
  <Linkify>{this.state.content}</Linkify>
</div>

上のフォームにこのように文字を打ち込んで見ましょう。ドメイン名からもリンク化できますね

URLからリンクを作ってみましょう。
https://www.google.com
ドメイン名からもリンク化します。 yahoo.co.jp

別タブで開きたい場合などはpropsで指定してあげます。

<div style={{ marginTop: '10px' }}>
  <Linkify properties={{ target: '_blank', style: { color: 'red', fontWeight: 'bold' } }}>
    {this.state.content}
  </Linkify>
</div>

詳しくは公式ドキュメントで

http://tasti.github.io/react-linkify/

自作のリンク作成コード

次に、自作のリンク生成コードを作ります。 外部ライブラリを使わない分ファイル容量が軽くなるのは間違い無いでしょう。

結構複雑になったので先に完成版のコードを書いておきます。

import React, { Component } from 'react'

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      content: '',
    }
    this.handleFormInputChanged = this.handleFormInputChanged.bind(this)
  }

  handleFormInputChanged(event) {
    this.setState({
      [event.target.name]: event.target.value,
    })
  }

  strip_tags(input) {
    const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi
    return input.replace(tags, '')
  }

  url_converter(input) {
    const regExp = /(https?:\/\/\S+)/g
    return input.replace(regExp, '<a href="$1" target="_blank">$1</a>')
  }

  render() {
    return (
      <div style={{ textAlign: 'center' }}>
        <div>サンプル</div>
        <textarea
          name="content"
          value={this.state.content}
          onChange={this.handleFormInputChanged}
          style={{
            width: '400px',
            height: '300px',
            fontSize: '15px',
          }}
        />
        <div style={{ marginTop: '50px' }}>
          <div
            dangerouslySetInnerHTML={{
              __html: this.url_converter(this.strip_tags(this.state.content)),
            }}
          />
        </div>
      </div>
    )
  }
}

export default App

コードの解説をすると、strip_tagsでタグを全て削除します。

strip_tags(input) {
  const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi
  return input.replace(tags, '')
}

url_converterでURLをリンクに変換します。

url_converter(input) {
  const regExp = /(https?:\/\/\S+)/g
  return input.replace(regExp, '<a href="$1" target="_blank">$1</a>')
}

使うときはこの様に呼び出して使えばURLをリンクにすることができます。

<div
  dangerouslySetInnerHTML={{
    __html: this.url_converter(this.strip_tags(this.state.content)),
  }}
/>

テキストエリアにこの書き込みを入れると、XSSに対処して、ちゃんとURLがリンクになっています。

<script>alert("1");</script>
<a onmouseover="alert(1)">XSS</a>
<iframe src="javascript:alert('1');"></iframe>

こんにちは http://www.google.com これがグーグルのリンクです

問題なさそうですね。

まとめ

正直dangerouslySetInnerHTMLは使いたくないですけど、ちゃんと正規表現でタグを除外してあげれば、問題ないでしょう。

スポンサードリンク

「為になったなぁ」と思ったら、シェアお願いします。

-プログラミング
-, ,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

railsのmodelで各データが何個あるかカウントする

概要 皆さんこんにちはcandleです。 今回は、railsでレコードのデータそれぞれ何個あるのか数える方法を紹介します。 mysqlだと簡単なのですが、railsだとよくわからなかったので、いろいろ …

react-modalの背面のスクロールを固定する

概要 みなさんこんにちはcandleです。今回はreact-modalの背面がスクロールした時に動いてしまう問題を解決してみたいと思います。 前提 reactの知識がある 完成版のサンプルコード サン …

CakePHPでhelloworld

概要 CakePHPでプログラミングのお約束helloworldを行いましょう。 helloworldとは動作確認も含めた、一番最初に書くプログラムコードです。 だいたいはhello worldと単純 …

MysqlのSELECT FROMの結果を美しく、見やすく表示する

概要 みなさんこんにちはcandleです。今回はmysqlのデータベースに関する簡単な記事です。 データベース系の言語は最近、様々出てきましたが、私は未だにMysqlくらいしか触っていません。 私はp …

wordpressのコメントフォームを編集し、名前とメールのデフォルト値を設定する

概要 みなさんこんにちはcandleです。今回はwordpressのコメントフォームをオーバーライドしてフォームの「名前」「メールアドレス」「url」のデフォルトバリューを設定したいと思います。 コメ …

  • English
  • 日本語

ベンチャー企業のCTOをやってます。大学時代にプログラミングを始め、javaから入門し、C++へて、PHPへと進み、会社ではRailsを使用。自動化が大好きなプログラマー