はじめに
みなさん、こんにちは torihaziです
今日は実装するのに1週間苦しんだEditorjsを
少し触ってみて魔改造とは言わないまでも
色々できるようになるまでいじり倒してみたいと思います。
ちなみに実務の方では、
エディタの中身をbackendにsubmitしたり、それをgetしてエディタに読み込ませたものを表示したり
編集したりというところまではできるようになりました。
ただ現時点、あんまり理解できていません。
あと日本語のドキュメントがあまりに少なすぎます。
これを書き切ってまとめられるようになったらqiitaにでもあげたいと思います。
使用するもの
Docker使ってNextjsの環境構築を行います。
https://torihazi.hateblo.jp/entry/2024/09/01/154108
ここの環境構築欄に書いてあるのでよかったら使ってください
入れてみる。
最初はまずものを入れてみるところから。
npm i @editorjs/editorjs --save
で~/features/editor/editos.ts
作ってその中に
import EditorJS from "@editorjs/editorjs"; const Editor = () => { const editor = new EditorJS(); return <div id="editorjs"></div>; }; export default Editor;
と書いてみる。
で。
この後どうするの。
実務では特に理由もわからず、useEffect使って
その中でインスタンス化して、ref使って、・・・ってしてたけど。
なんでそもそもuseEffectとかって書くのだろうか。
react-editor-jsという非公式の物があるので、ヒントを得に覗いてみる。
よくわからなかったので、とりあえず書いてみた。
ちなみに書いたところはpages/edit.tsxというところ。
import EditorJS from "@editorjs/editorjs"; const Editor = () => { const editor = new EditorJS(); return <div id="editorjs"></div>; }; export default Editor;
そうするとエラーが。
ReferenceError: Element is not defined
Claudeに聞いてみると
Element が定義されていないというエラーは、ブラウザ環境で通常利用可能な Element インターフェースが見つからないことを示しています。 これは、このコードがサーバーサイドで実行されようとしていることを示唆しています。 サーバー環境には Element や document などのブラウザ API が存在しません。 Next.js のようなフレームワークを使用している場合、デフォルトでページコンポーネントはサーバーサイドレンダリング(SSR)されます。 EditorJS はクライアントサイドのライブラリであり、ブラウザ環境を前提としています。サーバーサイドで実行しようとすると、このようなエラーが発生します。
なるほど。じゃあそのクライアントの方でやればいいのでは。
https://qiita.com/TaikiTkwkbysh/items/3a353b306b2eabd5205e
ということでuseEffectで囲んだ。
import EditorJS from "@editorjs/editorjs"; import { useEffect } from "react"; const Editor = () => { useEffect(() => { const editor = new EditorJS(); }, []); return <div id="editorjs"></div>; }; export default Editor;
結果変わらず。
ただこうするとエディタが表示された。
import { useEffect } from "react"; const Edit = () => { useEffect(() => { import("@editorjs/editorjs").then((EditorJS) => { const editor = new EditorJS.default(); }); }, []); return <div id="editorjs"></div>; }; export default Edit;
見えにくいが、2つエディタが表示されている。
エディタが2つ表示される原因
※ちなみにエディタが2つ表示される原因はStrictmode : trueだかららしい。
つまり開発環境が故に起こるもの。
確かにnext.config.mjsにおいてreactStrictModeをfalseにしたら消えた。
https://zenn.dev/nicopin/articles/3bbeb6f31347ef
さっきあげた成功コードの何がアレなのかよくわからないので
自分なりに調べてみた。
変わったこととしては3つ
- useEffectを使った
- モジュールを動的importした
1つ目、useEffectについて。
これはEditorjsのようなクライアントサイドでしか動かないライブラリは
クライアントサイドで動かす必要があり、それを可能にするのがuseEffectだから。
あとはuseEffectを使うとDOM要素が描画されたあとにインスタンス化できるからだ。
どういうことかというと、
Editorjsはインスタンス化するときにholderに設定したidを持つDOM要素を
探しに行って存在確認をしているらしい。
https://github.com/codex-team/editor.js/blob/next/src/codex.ts#L24
のconst editor = new Core(configuration);
のなかで呼んでいる
https://github.com/codex-team/editor.js/blob/next/src/components/core.ts#L1
のthis.validate();
だ。
このvalidateが中で$.get(holderId)
たるものを実行し
このget自体がjavascriptでお馴染みのgetElementByIdを実行している。
もちろんそのvalidateで発行しているエラー文が出てきてないので
断言はできないが、原因の1つではあると思う。
2つ目の動的インポートについて。
これは正直よくわからなかったのでClaudeに聞いた。
最初の方法、いつものように最上部でimportする静的importは
=> SSRではサーバ上で行われる可能性がある
=> Editorjsはクライアントでしか動かないからエラーになった
そしてuseEffectはコンポーネントのマウントが終わったあと、
つまりクライアントサイドで行われ、そのタイミングでimportするには
動的importしか方法がないのでこう書いた、というもの。
まぁ確かに論理は通っている気がするのでそういうものだと理解することにした。
ただ調べてみるとrequireでも動作した。
import { useEffect } from "react"; const Edit = () => { useEffect(() => { // import("@editorjs/editorjs").then((EditorJS) => { // const editor = new EditorJS.default(); // }); const EditorJS = require("@editorjs/editorjs"); const editor = new EditorJS.default(); }, []); return <div id="editorjs"></div>; }; export default Edit;
nextjsのdynamicという方法もあるみたいだが、うまく動かなかった。
なぜだろう。
まあいいか。一旦。
とりあえず表示できたし。
終わりに
だからuseEffectを使うのか。
クライアントサイドだから、サーバサイドだからとかが関係していたんですね。
あとuseEffectの本来の挙動とかあんまり理解していなかったので
良い勉強になりました。
とりあえず自分なりに導入してみてわかったことは3つ。
- Editorjsはクライアントサイドでしか動かないしインスタンス化時点でholderに設定したidを持つDOMが描画されている必要があるから、それを満足するuseEffect使え
- useEffectに伴い、動的importも必須
- 重複で描画されるのは StrictModeが原因
ということ。
てか実務でuseEffectのなかでtypeof window !== undefined
の分岐を入れてたけど
今日のことからこれは確実にtrueなのか。あーあ。まぁいいか。
なんでref使うのかとかはまた明日。