現状
umiJSのcommentにリンクを挿入する
renderItem={}
内のcomment
タグがチャットメッセージコンポーネントに該当する。
その中のcontent
プロパティにメッセージが渡されている。
問題点
item
を受け取ってtext
を表示している。
問題はここが全てstring
型で渡されているので、URLを添付してもリンクが繋がらない。
// 修正前
<List
/* 省略 */
renderItem={item => {
return (
<li>
<Comment
author={item.username}
content={item.text} // <== ここ
/>
</li>
)
}}
/>
実装方針
入力された文字列からURLを検索し変換してからcontent
に渡す。
正規表現を用いてURLライクな文字列を抽出し、aタグに置換する。
const link = (str) => {
const regexp_url = /https?:\/\/[a-zA-Z0-9.\-_@:/~?%&;=+#',()*!]+/g;
const regexp_makeLink = (url) => {
return '<a href="' + url + '">' + url + '</a>';
}
return str.replace(regexp_url, regexp_makeLink);
}
str
を受け取ってreplace(検索文字, 置換後)
メソッドで置換する。
regexp_url
を正規表現で定義して、https://~~~という文字列を検索する。
末尾の/g
はテキストないの全てのurlを検索するもので、urlがヒットするたびにregexp_makeLink
メソッドが実行される。
replace
メソッドは第一引数が席表現の場合、第二引数の関数の引数は(文字列, n番目の()で括られた文字列)
を受け取る。
regexp_makeLink
は受け取ったregexp_url
をaタグで囲う関数。
そしてautoLink
関数で処理したitem.text
をComment
のcontent
プロパティに渡す。
// 修正前
<List
/* 省略 */
renderItem={item => {
return (
<li>
<Comment
author={item.username}
content={link(item.text)} // <== ここ
/>
</li>
)
}}
/>
この時点で予期しない挙動に気づく...
<a href="' + url + '">' + url + '</a>
がそのまま文字列として表示されてしまう。
現状は全て文字列として渡されてしまっているものをJSX.Element
として渡さないといけない。
セキュリティ上あまり良くないような気がするが、InnerHTMLで埋め込む方針をとる。
Reactの場合、dangerouslySetInnerHTML
という書き方になる。
const link = (str: string): JSX.Element => {
const regexp_url = /https?:\/\/[a-zA-Z0-9.\-_@:/~?%&;=+#',()*!]+/g;
const regexp_makeLink = (url: string): string => {
return `<a href=${url} target="_blank" rel="noopener noreferrer">${url}</a>`;
}
const replacedString = str.replace(regexp_url, regexp_makeLink);
return (
<p dangerouslySetInnerHTML={{__html: replacedString}}></p>
)
};
__htmlプロパティに先ほど変換したURLを含む全ての文字列を渡すことで、pタグのなかにaタグを含む文字列を入れる。
ちなみに新規タブで開きたかったのでtarget="_blank"
を追記。
加えて、「開かれた新規タブ側で、リンク元タブの URL を操作できてしまう」という虚弱性対策として rel="noopener noreferrer
を加える。