react-hook-quillの紹介
はじめに
react-hook-quillというQuillの軽量ラッパーを開発したので紹介します。
QuillはSlabが開発をしているリッチテキストエディタです。QuillはReactなどのライブラリの上に構築されておらず、TypeScriptで開発されており、Reactで使うためにはReact用の実装を追加する必要があります。
すでにいくつかのReactとQuillの連携用のライブラリはありますが、Quillの提供するAPIが連携用のライブラリ側でふさがれていることがある等、ユースケースによっては課題が生じそうなものがあったため、新たに開発を行いました。
下記はreact-hook-quillの利用例です。
import { memo, useRef } from 'react';
import 'quill/dist/quill.snow.css';
import { Delta } from 'quill';
import { useQuill, usePersistentDelta } from 'react-hook-quill';
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const { persistentDeltaSetting } = usePersistentDelta(
{
containerRef: ref,
options: {
theme: 'snow'
}
},
new Delta().insert('Hello Quill')
);
// Quillのセットアップ
useQuill({ setting: persistentDeltaSetting });
return (
<div ref={ref} />
);
});
react-hook-quillの設計について
useEffect
の使用
react-hook-quillではuseQuill
内のuseEffect
でQuillの初期化とクリーンアップを行っています。useEffect
を使用しなくてよいケースは多々ありますが、今回はQuillを外部のシステムとして扱い、useEffect
を使用する方法がフィットしました。
ただし、カスタムフックでuseEffect
を隠ぺいしてしまうと、利用者側がuseEffect
が使用されていることを認識しづらい課題が生じます。この課題に関してはドキュメントに明記するという方法を取っています。
ユーザー編集の課題
useQuill
ではアンマウントと再レンダー時にクリーンアップ処理が走るため、例えばQuillで編集中に親コンポーネントが再レンダーされると、編集内容も初期化されます。
処理の整合性を保つためには正しい挙動ですが、Quillで編集するユーザーからすると編集中の内容が初期化されることは意図しない挙動になります。
この課題に対応するためreact-hook-quillではusePersistentDelta
とuesSyncDelta
という2つのカスタムフックを用意しています。
usePersistentDelta
を利用すると、React側ではユーザーの編集を関知せずに、なおかつコンポーネントの再レンダー時には編集内容を維持します。内部的にはクリーンアップ時に編集内容を一時的に退避し、次の初期化時にセットし直します。
usePersistentDelta
の利用時はReact側ではユーザーの編集を関知しないため、編集のたびに再レンダーが走ることも起きません。
例えば保存時にいままでの編集内容を使用したいだけの場合、編集内容をReact側のステートとして持つ必要はありません。
仮に上記のユースケースであえて編集内容をReat側のステートとして管理する場合は編集のたびに再レンダーが発生するためオーバーヘッドになります。
また、コンポーネント側でステートを持ちたい場合のためにuseSyncDelta
も用意しています。
react-hook-quillでは上記2つのカスタムフックはあくまでオプションとして用意しているため、Quillの初期化とクリーンアップが責務のuseQuill
のみを使用し、それ以外は自前で実装もできます。責務ごとにカスタムフックを分けて組み合わせられるようにすることで、異なるユースケースに応じて柔軟に対応できる設計を目指しています。
まとめ
react-hook-quillの外観をまとめると下記のようになります。
react-hook-quillによってより柔軟にQuillをReactに組み込み、Quillを最大限活用できるケースが増えればと思っています。
Discussion