Reactで生HTMLを自由自在に加工する ![eyechach\_takuru\_アートボード 1.png](https://image.docbase.io/uploads/c8bcdcee-26ee-47bb-8bf1-9fa77765d3ec.png =600x) こんにちは。クレイの浅海です。最近の休日は3歳児とマイクラをしています。 さて、業務ではここ1年ぐらい、[DocBase](https://docbase.io/?utm_source=kray.jp&utm_medium=blog&utm_campaign=20210518&utm_content=html_react_percer)のフロントエンドのフレームワークをBackbone.jsからReact.jsに変更する作業をしていました。 **完全に作り直しです。2万行を超えるBackboneのコードとお別れをしました。**バイバイバックボーン。 詳細 → DocBaseのフロントエンド改修をどのように進めたか ## Reactで実装する上で苦労した点 DocBaseにはReactとマッチしない機能もいくつかあり、Reactで実装する上で苦労しました。その一つが、メモの表示です。「メモ」というのは、DocBaseの投稿の単位です。[メモは例えばこのような投稿](https://help.docbase.io/posts/13697?utm_source=kray.jp&utm_medium=blog&utm_campaign=20210421&utm_content=html_react_percer) メモの内容は、ユーザが投稿したmarkdownをサーバでHTMLに変換しデータベースに保存、それを表示時にサーバから受け取ったHTMLをReactが表示することになります。 ただ、この受け取ったHTMLを表示する場合に、Reactにおいては1つ問題があります。ReactはデータからHTMLを生成するのであって、生HTMLを扱うことには向いていないのです。 一応、Reactで`dangerouslySetInnerHTML`を使用してHTMLをそのまま扱う方法もあります。 なのでメモをReactで表示したい場合は次のようにするだけで実装が完了・・ ```jsx
``` **・・しません!** 残念ながらDocBaseの仕様はそんなに単純ではなく、次のようにサーバから取得したHTMLを加工するいくつもの仕様が存在します。 * 外部へのaタグに対してnoreferrerを付与する * twitterの埋め込みを有効化する * mermaid.jsが記述されていたら有効化する * MathJaxの構文が書かれていたら有効化する * 差し込み用のdivタグを探して別のメモを入れ子で表示する * imgタグで表示する画像のサイズを最適にする などなど、他にも様々な加工が必要です。 これらを実現するために`dangerouslySetInnerHTML`でとりあえず表示して、それをReactの`Ref`から`ref.current.querySelector(query)`などしてDOMをごにょごにょと処理する・・、そんなのは辛いですね。折角、Reactで実装するというのに・・。 そんなとき、[html-react-parser](https://www.npmjs.com/package/html-react-parser)を使うと、良い感じにHTMLを加工できます。 https://www.npmjs.com/package/html-react-parser ## html-react-parserの使い方 ### バージョン情報 本記事執筆時の検証で使用した各ライブラリのバージョンです。 * react 17.0.2 * html-react-parser 1.2.4 ### 簡単な問題 まずは、html-react-parserの使い勝手を見るために、問題を非常に単純にしてみます。 やりたいことは次の1つだけとします。 * aタグに対してnoreferrerを付与する この要求を実現するhtml-react-parserの使い方は次のようになります。 propsとして受け取ったhtmlをhtml-react-parserで変換処理を行い、その結果を表示するだけのReactコンポーネントを用意しました。 ```jsx import parse, { domToReact } from 'html-react-parser' const replace = (node) => { if (node.name === 'a') { return ( { domToReact(node.children) } ) } } export const App = ({ html }) => { return (

変換前のHTML

{ html }

変換後のDOM

{ parse(html, { replace }) }

); } ``` どうでしょうか、このコンポーネントを実行すると、`props.html`に渡したhtmlのaタグにreferrer属性がついて出力されていることがわかると思います。 こちらに動作サンプルを用意しました。 [動作サンプル: CodePen](https://codepen.io/ttakuru88-the-sans/pen/GRrXEmm?editors=0010) html-react-parserには`parse`という関数が用意されています。このparseにはreplaceオプションとして関数を渡すことができ、パースされた結果のノード単位でreplace関数が実行されるので、どのようなタグにどのような加工を施すかを細かく実装できます。 replace関数がJSX.Elementを返せばnodeをそれで置き換えますし、何も返さなければnodeは置き換えません。 reaplce関数内で使っている`domToReact`を使っている理由ですが、node変数の中身はJSX.Elementではないので、nodeをJSX.Elementに変換するためです。 ### 仕様を増やす 上記のように、html-react-parserの`parse`を使って生HTMLを自在に加工できることがわかりました。単純な問題では上記の方法で問題が無いのですが、状況によっては注意する点があります。それを仕様を増やして確認してみます。 ここに2つめの仕様を追加しました。 * aタグに対してnoreferrerを付与する * **独自のtodayタグがあった場合は現在時刻を差し込む** ``というタグがあった場合、これを``に差し替えるという仕様を追加しました。このtodayタグに関する判定をreplace関数に追加します。 具体的には、次の用に実装します。 ```js const replace = (node) => { if (node.name === 'a') { return ( { domToReact(node.children) } ) } // 以下追加 else if (node.name === 'today') { return () } } ``` todayというタグだった場合は、`