Gyazzみたいなエディタ作りでReact.jsを学ぶ: TypeScript/Deno化
from Gyazzみたいなエディタ作りでReact.jsを学ぶ
vite化
https://github.com/inajob/simple-inline-editor/compare/deno-inajob...vite?expand=1
ちょっと機能を足してしまったけどDeno向けに書かれたものをviteで動くようにしてみたinajob.icon
Reactの既存の資産を使うにはTypeScript/Nodeがいいかなと
ViteのはBunもサポートしているのかーinajob.icon
DenoとBunの位置づけが気になる
強引なDeno推し(感謝
takker.icon
今Denoで書き換えてます
強引なDeno推しを受けているwinajob.icon
気にはなるけど、採用するかは考えさせてください
大量の型エラーが発生している。おそらくバグってる
どうしてこんなにReact.forwardRefが使われているんだ
Ref.currentにRef[]をつっこむという見たことない手段使っている
しかもrendering毎にcreateRef()で初期化しまくっているので意味がなくなっている用に思う
あ、過去にも似たような質問してた
なるべくロジックには手を加えず、型エラー中心に直します
できた!
draft PRしておきます
https://github.com/inajob/simple-inline-editor/pull/1
JSからTSに変えただけなので、Nodeに戻すこともできます
その場合はimport ... from "....ts(x)"の.ts(x)を全て消し、deps.ts,deno.jsonc,deno.lock,.vscode/settings.jsonを消し、package.json,tsconfig.json,.eslintrc.json,prettier.jsonを入れる
あー,Nodeに切り替えると、TSとlinterとformatterの設定が必要になるのか……めんどくさい~takker.icon
react-scriptsでこの辺まるっとやってもらえてた印象inajob.icon
https://zenn.dev/nekoya/articles/dd0f0e8a2fa35f
もう古いのか、、
次はvite試してみるかな、、
[" import ... from "....ts(x)"の.ts(x)を全て消し]はどうやらいらなそう
"moduleResolution": "Bundler"でなんとかなるらしい?
またDeno側でNodeのように拡張子なしで書けるようになoptionが入ったらしい
https://deno.com/blog/v1.39#sloppy-imports
--unstable-sloppy-importsを渡して実行
わーい、勉強しますinajob.icon
聞きたいこと
popupHandlers[popup.index].handler()が動作しない可能性がある
TextareaWithMenuにてpopupHandlers[].handler()はある条件のとき引数なし函数に変換されるhttps://github.com/inajob/simple-inline-editor/blob/244f388d7c738130ad61b6748036cbbd3537e7ed/src/components/TextareaWithMenu.js#L115-L136
しかし条件に当てはまらないときは、selectを渡さないと実行時エラーを引き起こす
手元だと動くけど、型に厳密にやろうとするとエラーになる感じですかね?inajob.icon
なります。ただ実行エラーはおきないようですtakker.icon
select.selection === "" && candidate !== ""のときはpopupHandlersが{ name: string; hadler: () => void }[]に、そうでないときは{ name: string; handler: (select: TextFragment) => void }[]になる
勘違いしてた。そうでないときは{ name: string; handler: (select: TextFragment) => { target: { value: string; } } }[]になるtakker.icon
返り値がvoidなので型エラーになる
ただし、コード上は返り値があるかどうかのチェックを入れているので、実行時エラーは起きない
https://code2svg.vercel.app/svg/L225-229/https://raw.githubusercontent.com/takker99/simple-inline-editor/38a019a5024744c36b7aea771c3dc86608077263/components/TextareaWithMenu.tsx#.svg https://github.com/takker99/simple-inline-editor/blob/38a019a5024744c36b7aea771c3dc86608077263/components/TextareaWithMenu.tsx#L225-L229
handler()は返り値がないはずだが、L178で返り値を使っている
https://github.com/inajob/simple-inline-editor/blob/244f388d7c738130ad61b6748036cbbd3537e7ed/src/components/App.js#L18 みたいなのを想定してますinajob.icon
が、引数なし関数の方と一貫してないな・・
引数なしのほうは返してませんねtakker.icon
上の話もあるしselectを渡して返り値で行の書き換えを指示する方法に統一するほうが良さそうな気がしてます
勘違いしてました。{ name: string; handler: (select: TextFragment) => Event | void }[]という想定だったんですねtakker.icon
これなら問題ない
targetを指定してEventを作るのは無理があるので、ここの型定義を工夫する必要あり
React.ChangeEventも{ target: { value: string; } }も受け取れるように設計し直す
あっだめだ。Editor.jsのonChangeの定義でtarget.selectionStartなどを使っているから、戻り値を{ target: { value: string; } }にするとエラーになる
これ以上は、このあたりのロジックが何をしようとしているのか理解しないと修正できなさそう
ロジック見て修正してみましたinajob.icon
https://github.com/inajob/simple-inline-editor/pull/2/commits/a83a7f8d1f858cc7231e57722195b1ad46441075
props.setLine()の引数は{ prefix: string; selection: string; suffix: string; }のはずだが、stringを渡している箇所がある
setSelectのことかな?setLineだとしたら例をどこか示してもらえると理解できるかもinajob.icon
URL貼りましたtakker.icon
https://github.com/inajob/simple-inline-editor/blob/244f388d7c738130ad61b6748036cbbd3537e7ed/src/components/Editor.js#L171 で作った関数なので、string1つの引数が正解という認識ですinajob.icon
ごめんなさい、ここは自分のreadingミスですtakker.icon
LineProps.setLineとTextareaWithMenu.setLineの型定義を勘違いしていた
直しました
そういえば環境のこと書いてなかった気がするtakker.icon
今回はpackupでbuild & serveしています
これが主流かどうかは不明です
要はJSさえなんとか作れればいいので、手法が無数にあります
分類すると
自力でJSビルド
HTML/JS/CSSを生成する&hot reloadサポート
packup
https://deno.land/x/ts_serve@v1.4.4
DenoがJSXをサポートしているのでReactと親和性が高いですが、基本はどんなフレームワークとも組み合わせられます
React+SSR特化
Deno Flesh
Aleph.js
勉強してみる
inajob.icon
takker.iconさんからのPRについて、今後の作業
動作の確認
なんか動いてそう
❓SourceMapが生成されてないのでconsole.logの出力がどこの物かわからない
deno.jsoncにsourceMapの設定ができるようだが、設定しても有効になっている様子が無い
bundled codeを見ればだいたいわかりますtakker.icon
(packupの場合)型を搾り取ったコードを順番に羅列しているだけなので、minifyしたコードより素直です
minified codeでもformatすれば元のコードの構造とほぼ同じなので見当づきます
まぁ確かに・・inajob.icon
じっくり差分を見て感想を書く
TypeScriptをちゃんと読むのは初めてだと思う
そうなんだtakker.icon
inajob.iconさんがよく書くのはGoとかC++だっけ
最近はGoが多いけど、、人のコードを直したり、機能を足したりが多いのでなかなか体系的には学べていませんinajob.icon
よくない
仕事だとそーっと周りに影響を与えないようにメスを入れるみたいな謎の能力を求められる・・
こんな仕事ばかりやっていると1からコーディンする能力がどんどん下がっていく
下がるとそういう仕事が来なくなる・・
悪循環
フロントエンドをやっていた頃はJavaScriptをよく書いていました(結構昔なのでもう色々違っていますが・・)inajob.icon
Deno
deps.ts
ここに依存を寄せるのがDenoの1つのやり方らしい
freshとかのフレームワーク系はimport mapを使っている印象takker.icon
TypeScript
拡張子はtsx, tsを使い分ける必要あり
tsのファイルの中にJSXを書くとビルドが失敗する
React.FC?
Reactの型定義をいい感じにするものらしい
仕組みはよくわからんが、使い方は見たらだいたいわかる
useForwardRefがなにか込み入っている
https://stackoverflow.com/a/73748435 からとってきたものらしい
被せないと型エラーを解決できませんでしたtakker.icon
型をチェックしているIF文、nullを想定したプロパティアクセス
ifの条件でinstanceOfとかをAnd条件でつなげると、型推論してくれる
nullの可能性があるときにそのままプロパティにアクセスするのはNG
?. でアクセスする(オプションチェーン演算子)
非nullアサーション!
あんまりやりたくないtakker.icon
バグの元
React.ClipboardEventHandler<HTMLTextAreaElement>
イベントハンドラはこういう型になる
要素の種類も関係してくる
JavaScriptでのサンプルを見つけてきたときに、そのまま真似して書くだけでは済まないのがやや面倒
型の恩恵をどの程度感じるかだな、これは慣れかも
TypeScriptに書き換える代わりに、JavaScriptに//@ts-checkとJSDocを付けるだけでも同じ恩恵を得られますtakker.icon
そのままweb browserでもうごかせますし、まずはこっちのほうが楽かも
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/TextareaWithMenu.tsx#L170 ここのifでのチェックはあったほうが良いのはわかるけど、特にこれを書かなくても警告になったりしないように見える
❓こういうのは人間が気を遣って書くもの? or 何か検出する手段がある?
自動で警告が出るはずですがtakker.icon
settings.jsonの設定が足りないのかも
Denoを使う時のsetting.json
うーん、でも警告に関わる設定は上のどこにもないか
上を設定しても変化なさそうですinajob.icon
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Editor.tsx#L76 同じく?ここのdatasetが未定義の可能性も特にエディタでの警告が出ない
他でもifで事前にundefinedをチェックしている箇所の多くは手元で警告が出ない
https://github.com/takker99/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Line.tsx#L106 ここは自分の環境だと赤線が入っている
const result: React.ReactNode = []; で良さそう
本当だ。なんでエラーでなかったんだろうtakker.icon
https://gyazo.com/ad246a247f56891452eff5e95b1c46e3
あからさまなanyなのに警告が現れない
makeLineの戻り値は推論されているので、それでエラーがでてない?
ならinajob.iconさんの環境で検出されている理由がわからない……
もしかしてtypescriptの拡張機能が有効になっていたりしませんか?
あとでみますinajob.icon
右下のバーにTypeScriptと出ているのでそうっぽい
ファイルタイプ識別表示の可能性もありますtakker.icon
バーにDenoと出ていればDenoの拡張機能のほうが有効になっている
人の記事だけどこれか VSCodeでdeno - まさたか日記inajob.icon
Denoを有効にできてませんでした。上のsetting.jsonでいい感じになった予感
https://github.com/takker99/simple-inline-editor/blob/deno/components/Line.tsx#L37 このuseEffectの発火タイミングがTypeScript化前と異なっている
props.isFocusを監視対象に入れてお茶を濁す
useForwardRef()を噛ませただけですが、発火タイミングかわっちゃったかtakker.icon
その他
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffectで依存オブジェクトが無い事を強行する
それ無視しちゃだめな警告のような……takker.icon
あとで精査しよう、起動時に一回走れば良さそうな処理に見えるinajob.icon
その場合はuseEffect(() => {/* ... */}, [])を使ってくださいtakker.icon
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Editor.tsx#L170 の話ですね。このままで良いという理解inajob.icon
と思ったけどこのコメントを消してもdeno task fmtはエラーや警告が出たりしないな
あ、自分のコードですでにこれを書いていたのか、勘違い・・inajob.icon
他のプロジェクトからの取り込みがいい感じにできるか確認(もともとの目的)
雑に import { App } from "../simple-inline-editor/components/App.tsx" とかしたら読み込めた!
これでいいかな・・
CSSは何かモジュール化?した方が良さそう
これはDenoとかTypeScriptの範疇ではないので後で考えよう
@importのことかなtakker.icon
自分が良いものと感じたらマージする
取り込みがいい感じ、とかも観点だが、普通にTypeScriptにしたほうがメンテしやすいだろうという気がする
型がないと、引数に一体何が入ってきているのかわからなくなりますからねtakker.icon
手元でpullして少し編集したが、勉強になりそうだし、型があったほうがべんりそうだinajob.icon
一方でReact周りの型については(DOM周りの型と比べて)詳しくないので、始めはいろいろ躓きそう
型を気にしなければ動くのに、型パズルが謎でうまくビルドできないのはややストレス
React周りの型は自分もほとんど知りませんtakker.icon
hoverすると型がでてくるので、それを真似て書いています
https://gyazo.com/7a4bd9d3877c1829bf51122e5346c137
なるほど!inajob.icon
だいたいわかってきたので自分のブランチを作って作業していく
https://github.com/inajob/simple-inline-editor/pull/2
おお~いいかんじtakker.icon
eslintが動いていないなinajob.icon
setting.jsをちゃんと読んだ方が良い?
今はdenoをenableしているだけ
別にeslintにこだわりはないのだが・・denoにもlintがある?
ありますtakker.icon
deno lintとdeno fmt
lintは通常は何も設定しなくていいはずですが……
eslintの拡張機能が動いている場合は、ワークスペースのsettings.jsonで無効にしてください
fmtはdefault formatterをdenoに変えてください
なるほど、いままでReact.js独自のlintのルールみたいなのがあったのだけど、相当するものはあるかな、、inajob.icon
名前からこの辺かな?
eslint-plugin-react
eslint-plugin-react-hooks
eslintはそのへんですねtakker.icon
useCallbackとかで依存を書き忘れてる時とかに教えてくれたりしてた
残念ながらDenoにはありませんtakker.icon
lintにpluginシステムが存在しない
Denoのreact frameworkを使っている人はどうlintしているんだろう
https://github.com/denoland/deno_lint/issues/148
react対応要望
https://github.com/denoland/deno_lint/issues/175
pluginシステムを検討しているみたい
ぐぬぬinajob.icon
ここにこだわりがある場合はNodeとTypeScriptを使う感じになりそう
"moduleResolution": "Bundler"を使えば、.tsを消さずにNodeでも使えるらしいです?takker.icon
ちょっと表面を調べただけなので、地雷があるかは不明
自分は依存配列を手作業で入れてますtakker.icon
見逃しは……まああるかも
普通に知らないミスを教えてくれてたなぁという思い出があるinajob.icon
ループで作るHTML要素ににkeyをつけ忘れたり
やっぱりlintほしいですよねtakker.icon
文脈を理解してプログラムを修正して、その後質疑にも応じてくれるってすごいプログラミング学習支援サービスだよなと思う
人力LLMtakker.icon
めちゃいいですinajob.icon
設計面でいうと、useRef()を大量に使っている点が気になりますtakker.icon
Reactは大抵の場合useState(),useMemo(),useCallback(),useEffect()で事足りる
ちょっと複雑な状態遷移をさせたいときuseReducer使うくらい
useRef()の出番はDOM操作する場合など特殊な場合に限定される
ほうメモる.iconSummer498.icon
ロジック追わないと必要な処理なのかは明言できませんが、そのあたりで直感センサーが警戒反応を示しています
使用箇所をメモしてみるinajob.icon
日本語入力中かを保持するフラグ(Boolean)
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Editor.tsx#L39
これをuseStateにすると、再レンダリングされてしまって都合が悪かった
これはDOM操作ではない
改善できそうtakker.icon
ポップアップ要素
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Editor.tsx#L93
行選択の状況が変わった時に、ポップアップの位置を調節する(DOM操作)
各行のTextarea
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Editor.tsx#L173
フォーカスが移動したときに、今フォーカスのあるTextareaにfocusを与える(DOM操作)
もともとuseStateを使っていたが、何か微妙だったので今の形にした
a. useStateを使う場合、自分の行にフォーカスが当たってるかを示すpropをLine以下に持たせることになる
1つの行だけがtrueになるようにロジックで保証する
b. 今のやり方だとEditorが選択されてる行にfocusを実行する
どっちが自然かなと思って悩んでいる
aならforwardRefの配列もなくなるように思う
配列になっていて一見不気味だと思う
不気味だったtakker.icon
各行のDIV
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/Line.tsx#L227
マウスドラッグを検知したときに、DIV全体を選択する(DOM操作)
ここうまいことDIVのreact要素に通知出して選択させられそうtakker.icon
ポップアップ要素
https://github.com/inajob/simple-inline-editor/blob/9b339534ea97f3ed702173e94e0c8662825dacca/components/TextareaWithMenu.tsx#L182
文字選択の状況が変わったときに、ポップアップの位置を調整する(DOM操作)
document.Selectionを使っている都合でDOM操作が多くなっているみたいですねtakker.icon
UserCSSにより井戸端ページの編集が遅くなった