リッチテキストエディタライブラリSlateJSの状態・DOM管理方法

2019/11/28: Slateの新バージョンv0.50が公開されました。大まかな哲学は変わっていないようですが、APIは大きく変わるBreaking Changeとなりました。

https://scrapbox.io/tuttieee/SlateJS%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0 から転載

リッチテキストエディタライブラリ[SlateJS](https://www.slatejs.org/)を使っていて、
このライブラリがどのようにデータを管理し、DOMに反映し、まだDOMの変更をデータに反映しているのか
また、Controlled Componentとはどう違うのか
が気になったので、中身を読んでみたメモ。

# Slateの状態管理・React連携
* slate, slate-reactという２つのパッケージがメイン
    * `slate`: エディタの内部状態を管理する。`Editor`クラス（`slate/src/controllers/editor.js`）がメインのインタフェース。
    * `slate-react`[React]バインディング。`slate`の`Editor`クラスに格納された内部表現を使って[VirutalDOM]を構築する（`src/components/*.js`）。また、イベントをハンドリングして`Editor`クラスのコマンドを叩く。総じて、通常のReactのControlled Componentを作るときにやる処理を巻き取ってくれる（VDOMの宣言＋イベントハンドリング→state更新）。Controlled Componentにおけるstate更新が、slateでは`Editor`のコマンド実行になる。
* `slate`パッケージ
    * `Editor`クラス（`controllers/editor.js`）: Valueを保持したり、プラグイン機構を提供したりと、slateエディタの基盤となる汎用的なフレームワークを提供する。具体的な処理のほとんどはプラグインに抜き出されている。
    * `plugins`: プラグイン機構はユーザに解放されているが、コア機能の多くもプラグインを利用してslateに組み込まれている。
    * `models`: データモデルの定義も`slate`パッケージにある。
    * `commands`: 組み込みコマンド
* `slate-react`パッケージ
    * `src/components/editor.js`の`Editor`クラスが、Reactでslateを使うときのエントリポイント。`<Editor>`として使うときのクラス。
        * 内部で`slate`の`Editor`クラスのインスタンスを保持している（`this.controller`）。
        * renderでは、`RenderEditor`で外側を修飾した後は、`Content`クラスに移譲。
    * `src/components/content.js`
        * renderで`Node`コンポーネントに`document`を渡して描画する。`Node`は再帰的にValueツリーをrenderする。
            * ユーザが適宜カスタマイズできるよう、`renderBlock`, `renderInline`, `renderText`が`Node`で呼ばれる。
        * 子Nodeに起きたイベントは全て、`Content`（ルートノード）で拾う。`Content`クラスに全てのイベントハンドラが定義されている（`this.handlers`）。`this.handlers`は結局イベントの種類情報（`onInputBefore`など）を付与して、`this.props.onEvent`を呼ぶ
    * `src/components/editor.js`の`Editor.onEvent`: 上記Contentからコールバックされる。`onEvent`はさらに`this.controller.run`を呼ぶ。
    * `this.controller.run`は`slate`の`Editor.run`。登録されたプラグインからイベントハンドラを呼び出す。
    * `slate-react`の`plugins`でDOMイベントのハンドラを定義してある。これらのプラグインが上記`this.controller`に登録されているため、エディタを構成するDOMで起きたDOMイベントは全てこのハンドラに送られる。
        * 流れを整理すると、
            1. `Node`でDOMイベント発生
            2. Reactのイベントバブリングにより、`Content`（の`Container`コンポーネント）にDOMイベントが送られる
            3. `Content.handlers`で捕捉
            4. `Content`の親である`Editor`の`onEvent`にコールバック
            5. `Editor.onEvent`から`this.controller.run`=`slate.Editor.run`をコール
            6. `slate.Editor.run`がプラグインのイベントハンドラをコール
    * さて、`slate-react`のプラグインのうち、`AfterPlugin`(`src/plugins/dom/after.js`)を見てみる。これは上記のControlled ComponentでのアナロジーにおけるsetState相当のことを担当している。
        * `AfterPlugin`の`onBeforeInput`が、各種のイベントに応じて`editor`のコマンドをコールしている。
            * 例えば、`'insertText'`イベントの時は`editor.insertTextAtRange`
            * このイベントはネイティブDOMイベントである。ネイティブのbeforeInputイベントの`event.eventType`は https://triple-underscore.github.io/input-events-ja.html#dom-inputevent-inputtype を参照。多種多様なイベントが定義されており、HTML5のレイヤでcontenteditableを利用したリッチテキスト編集サポートが意識されている気がする。

# 単純なinput, textareaのControlled Componentとの違い
* Controlled Componentは、以下の3要素からなる
    * 1. データを何らかのstoreに保持（component state, useState hook, redux storeなど）
    * 2. データ→VDOMのマッピングの宣言（render関数）
    * 3. VDOMのイベントハンドラからデータを更新（this.setState、hookのsetXX、dispatch(action)など）
* slateのモデルでは、この1, 2, 3が以下に対応する
    * 1. データを`slate`パッケージの`Editor`に保持。その際のデータモデルも`slate`に定義される
    * 2. データ→VDOMのマッピングの宣言（render）は`slate-react`パッケージの`Editor` componentとその子たちで行われる。ユーザは適宜`render*`でrenderに介入できる
    * 3. 2. で定義したVDOMのイベントハンドラから、プラグイン機構を介して、`slate-react`のプラグインに定義されたイベントハンドラに処理が移譲される。これらのイベントハンドラから`slate`の`Editor`の各種コマンドが呼ばれる。コマンドはデータを書き換える操作を行う。
        * コマンドのデータ書き換えは、さらにOperationという操作を通して行われるが、ここでは触れない
* 以上のように、Controlled Componentではユーザが自分で全てを書ける部分を、slateが引き取ってくれている。contenteditableを使う場合、この部分の処理は圧倒的に煩雑になるのであろう。
    * Controlled Componentでもredux-formのようなラッパーがある。slateはこれらのラッパーと同じレイヤにいると考えられる。
        * inputやtextareaであれば、ユーザが自分でControlled Componentを書けるし、楽をしたければライブラリを使える。一方、contenteditableは自分で書くという選択肢はほとんど候補に挙がらないほど複雑でライブラリ利用一択なのであろう。
            * contenteditableの苦しみ: https://www.bokukoko.info/entry/2017/10/08/154950
            * とは言っても、自分で頑張る選択肢ももちろんある。[LINEBlog]や[note]は頑張ったようだ。https://engineering.linecorp.com/ja/blog/contentable-development-of-line-blog-apps/ https://note.mu/ct8ker/n/n037f6ba3c318
* この設計の帰結として、自由度がControlled Componentより低くなってしまっている。
    * データ管理・変更
     * Controlled Componentであれば、stateの持ち主はルートに近いノード。また、そこからstateを直接変更してもVDOMはそれを正しく反映する
     * slate-reactの場合、（Controlled Component風にvalue, onChangeが公開されているとはいえ）、勝手にvalueを変更するとエディタが壊れる。valueの変更はcommandで行うしかない。valueの持ち主は親Componentのように見えるが、持ち主にvalueを直接変更する自由はない。
         * slate-reactのvalue, onChangeはあくまでイベントハンドリングや保存用データを取得するためのハッチであって、Controlled Componentのようにvalueの直接的な変更権まで親に移譲されるものではないように思われる。
    VDOMのrender
     * Controlled Componentであれば、データをどのようにViewにマッピングするか（renderの設計）は完全に自由
     * slateでは`render*`フックを使えるのみ

# SlateJSの"command"という設計について
* HTML5に`execCommand`が定義されている。contenteditableと同時に使うといい感じらしい
    * https://blogs.msdn.microsoft.com/osamum/2014/02/13/html5api-contenteditable/
* slateの、「commandを通して対象を操作する」デザインはこの辺りから来ているのかもしれない。`Block`, `Inline`, `Text`といったデータ構造もDOMを真似ている。全体として、DOMのアナロジーで書けるようにデザインされている？
* [コマンドクエリ責務分離] ([CQRS]: Command and Query Responsibility Segregation) も関係？
		https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/apply-simplified-microservice-cqrs-ddd-patterns
    * SlateJSのcommand/query: https://docs.slatejs.org/guides/commands-and-queries