ReactとVanillaJSの共存

#14
2022.7.3

最近Fractalというメモ帳アプリの開発を試みています。このアプリはElectronとReactを使って書いているのですが、WYSIWYGをReactで実装するには多くの課題があります。一番の課題が、contenteditableです。Reactでcontenteditableな要素の子を渡す方法には、dangerouslySetInnerHTMLを用いる方法と、childrenを用いる方法があるのですが、childrenを使って子要素を渡す場合、かなり面倒なことになります。

まず、Reactの流儀に外れず、ユーザからの入力の度、内部のデータを変更してアップデートを発生させるのですが、このアップデートの際、画面上のカーソルの位置がリセットされてしまいます。なので、アップデート前にカーソルの位置を計算し、アップデート後にその位置に戻す処理が必要になります。このカーソルの位置を取得するための、Selectionの操作が結構大変です。

加えて、仮想DOMとの整合性を取る必要があります。例えば、<b>要素がcontanteditableの中にあった場合、それがユーザによって削除されると、Reactが持つDOMツリーと実際のDOMツリーが異なるのでエラーになります。これを対策するためには、ユーザが<b>を含むテキストを削除しようとするのをpreventして、(アプリがステートとして持つ)AST上からアプリケーション側で選択されている該当部分を削除した上でアップデートする、という処理を実装しなければならないのです。

おまけに、Reactでcontenteditableな要素を他から操作する(focusしたり)する場合、forwardRefまみれになってコードが複雑化します。

ということで、Reactでcontenteditableを使うと、あまり旨味がないのです。良い解決策がないか考えていて気づいたのが、ドキュメントを表示する領域は、Reactで描画する必要はないのではないか、ということです。表示領域だけ、Reactに描画してもらい、そこからrefを取得して、DOMを直接操作すれば、Reactのライフサイクルに巻き込まれる必要がなく、柔軟に扱うことができます。