インストーラーをダウンロードするところに統計情報や障害レポートの自動送信のチェックボックスがあります
インストールするとき exe を実行してから Chrome が起動するまでには選択肢が出ないです
ということは exe ファイルが別?と思ってインストーラーを比較してみました
ファイル名はファイルサイズは一緒ですが ハッシュ値が異なってました
ダウンロード URL もよくみると別になってました
言語などいろいろな情報がクエリ形式で含まれていて その中の usagestats という統計情報の送信の有効無効のフラグが 1 か 0 で異なってました
インストーラーの exe を使い回したり共有したりすると その確認がされないのはどうなのかなと思ったりはしますね
テストを実行するときって 並列にすると色々問題が出そうでとりあえずひとつずつ順番に実行するようにしてます
純粋な関数なら問題ないですが 実際にはファイルや DB など外部に副作用を与える物が多いですからね
こういう処理が入るものを並列にすると問題が起きます
1 つ目のプロセスでデータを作って それを確認する前に 別のプロセスで上書きしてしまったり
同じ場所を使わなければいいので 並列に処理する数だけ フォルダやサーバーを用意すればいいといえばそうなのですが 結構面倒です
処理ごとにフォルダを作ったり DB や通信相手のサーバーを作ったりは大変です
ですが コンテナなら自然と独立した環境になります
アプリ側の処理の中では普通にファイルを書き込んだりサーバーを起動してアクセスしたりするだけです
でもそういう機能のテストランナーってあるのでしょうか
特に聞いた覚えがないです
テストランナーの処理の中で並列にするところは個別にコンテナを起動とか考えると複雑そうですし対応してるのがないのかもしれません
並列にしたいところはファイルを分けて 手動でファイルごとにコンテナを作ってその中でテストランナーを実行するようにするのがいいのかもです
でもこれはこれで面倒な気がするのですよね
純粋な関数なら問題ないですが 実際にはファイルや DB など外部に副作用を与える物が多いですからね
こういう処理が入るものを並列にすると問題が起きます
1 つ目のプロセスでデータを作って それを確認する前に 別のプロセスで上書きしてしまったり
同じ場所を使わなければいいので 並列に処理する数だけ フォルダやサーバーを用意すればいいといえばそうなのですが 結構面倒です
処理ごとにフォルダを作ったり DB や通信相手のサーバーを作ったりは大変です
ですが コンテナなら自然と独立した環境になります
アプリ側の処理の中では普通にファイルを書き込んだりサーバーを起動してアクセスしたりするだけです
でもそういう機能のテストランナーってあるのでしょうか
特に聞いた覚えがないです
テストランナーの処理の中で並列にするところは個別にコンテナを起動とか考えると複雑そうですし対応してるのがないのかもしれません
並列にしたいところはファイルを分けて 手動でファイルごとにコンテナを作ってその中でテストランナーを実行するようにするのがいいのかもです
でもこれはこれで面倒な気がするのですよね
Fastify の CLI を使うと自分が書く部分はプラグインになります
プロセスのエントリポイント部分はライブラリ側で行われるのでウェブサーバーとしての処理しか書けないです
普段は Node.js のウェブサーバーをたてるときは ウェブサーバーとしての処理以外にもプロセス内でいろいろなことをしています
index.js ではそれらをセットアップして その中の一つとしてウェブサーバーもあるという感じです
ウェブサーバー以外のものは 例えば DB やログ等の定期的なクリア処理です
古いデータを消したりローテートしたりを 24 時間間隔などで行います
Node.js はタイマー処理がしやすいので cron 等に任せるより楽です
また ウェブサーバー以外で外部サービスからの通知類を受信したりすることもあります
Redis や PostgreSQL の通知の仕組みだったり 専用の TCP 通信だったりで外部と通信しています
受けたデータによってウェブサーバー側の処理を変更したりしますし DB からキャッシュを再取得するとかもあるので 同じプロセスの方が都合がいいです
別プロセスに分けると ウェブサーバーに API を設けて内部的にそこにアクセスして変更を伝えるようなことになり二度手間感があります
反対にウェブサーバーが受けたリクエストに応じて 通知を受信してる処理を変更するケースもあります
外部通信の ON/OFF の切り替えなどをするならやっぱり同じプロセスの方が扱いやすいです
分けることもあるのですが それでもウェブサーバー側が親で外部通信系が子となる関係で リクエストに応じて子プロセスを止めたり起動したりというのが多いです
systemd でそれぞれ別サービスにすることもできますが 分けすぎても扱いづらいのと ウェブサーバーのプロセスから外部通信プロセスを再起動したいみたいなときに systemd を通すと面倒なのですよね
以前は PM2 を通して systemd は PM2 を起動して ウェブサーバーは PM2 と通信して別プロセスを制御してましたが systemd だけで済ませられたほうが楽です
そうなると systemd がウェブサーバーを含むプロセスだけ起動して そこで全部をやるか一部をそのプロセスのサブプロセスにするかになります
他にも WebSocket サーバーも使うなら それらを統合する必要がありますし ロガーや DB のコネクションなどをウェブサーバーと WebSocket サーバーで共有するなら一括してそれらの外側で作って管理したいです
そんなわけで Fastify CLI って簡単なものでウェブサーバーだけならいいのですが 他にもやりたい場合に不便に感じます
でも PHP などはこういう状態になるわけですし 全部分けて別プロセスって割り切るほうが良いこともあるのでしょうか
Node.js はリクエスト間で変数の空間が分かれないので積極的にキャッシュしていけるのが魅力ですが プロセスが分かれるとキャッシュの更新ができずその利点が活かせなくなるのが困るところなんですよね
プロセスのエントリポイント部分はライブラリ側で行われるのでウェブサーバーとしての処理しか書けないです
普段は Node.js のウェブサーバーをたてるときは ウェブサーバーとしての処理以外にもプロセス内でいろいろなことをしています
index.js ではそれらをセットアップして その中の一つとしてウェブサーバーもあるという感じです
ウェブサーバー以外のものは 例えば DB やログ等の定期的なクリア処理です
古いデータを消したりローテートしたりを 24 時間間隔などで行います
Node.js はタイマー処理がしやすいので cron 等に任せるより楽です
また ウェブサーバー以外で外部サービスからの通知類を受信したりすることもあります
Redis や PostgreSQL の通知の仕組みだったり 専用の TCP 通信だったりで外部と通信しています
受けたデータによってウェブサーバー側の処理を変更したりしますし DB からキャッシュを再取得するとかもあるので 同じプロセスの方が都合がいいです
別プロセスに分けると ウェブサーバーに API を設けて内部的にそこにアクセスして変更を伝えるようなことになり二度手間感があります
反対にウェブサーバーが受けたリクエストに応じて 通知を受信してる処理を変更するケースもあります
外部通信の ON/OFF の切り替えなどをするならやっぱり同じプロセスの方が扱いやすいです
分けることもあるのですが それでもウェブサーバー側が親で外部通信系が子となる関係で リクエストに応じて子プロセスを止めたり起動したりというのが多いです
systemd でそれぞれ別サービスにすることもできますが 分けすぎても扱いづらいのと ウェブサーバーのプロセスから外部通信プロセスを再起動したいみたいなときに systemd を通すと面倒なのですよね
以前は PM2 を通して systemd は PM2 を起動して ウェブサーバーは PM2 と通信して別プロセスを制御してましたが systemd だけで済ませられたほうが楽です
そうなると systemd がウェブサーバーを含むプロセスだけ起動して そこで全部をやるか一部をそのプロセスのサブプロセスにするかになります
他にも WebSocket サーバーも使うなら それらを統合する必要がありますし ロガーや DB のコネクションなどをウェブサーバーと WebSocket サーバーで共有するなら一括してそれらの外側で作って管理したいです
そんなわけで Fastify CLI って簡単なものでウェブサーバーだけならいいのですが 他にもやりたい場合に不便に感じます
でも PHP などはこういう状態になるわけですし 全部分けて別プロセスって割り切るほうが良いこともあるのでしょうか
Node.js はリクエスト間で変数の空間が分かれないので積極的にキャッシュしていけるのが魅力ですが プロセスが分かれるとキャッシュの更新ができずその利点が活かせなくなるのが困るところなんですよね
Node.js の前段に nginx を置いたほうがいいのか 新しくサーバー環境を作るたびに考えてる問題です
これまでは nginx は無しで Node.js で全部やることにしていました
Node.js 内で全部やるほうが柔軟にできますし サーバーの追加は面倒です
間に追加のレイヤーが増えるほうが複雑化しますし 処理が増える以上パフォーマンス面でも劣りますし 単純に管理するものも増えます
設定ミスやバグなどでうまく動かないとかが出る可能性はありますし シンプル化できるものはシンプルにしておきたいという考えです
ただ Fastify では 前段に nginx などを置くことを推奨しています
たまには置いてみようかな ということで試してました
やってみると たしかに nginx を使ったほうが 圧縮とかキャッシュとか証明書や TLS バージョン等の HTTPS 関係やアクセスログなどを Node.js 側でやらなくていいので Node.js 側は楽になりました
静的ファイルも nginx に任せてしまうと Node.js は完全に API だけに専念できるので いつも入れてるライブラリ類も結構減りました
Node.js 側をシンプルにするという意味では良さそうです
Node.js で全部やると柔軟にできるといっても フレームワークの都合で うまくやりたいように設定できなくて遠回りに自分で制御してたりしたので これでいいような気もしてきました
静的ファイルのサーブとルートの処理の優先順とか SPA に対応させるとか フォルダごとにキャッシュ設定変えたりとか こういうことって結構面倒だったりしますし
それにフレームワークを変えるコストも減ります
Node.js ですべてやると静的ファイルのサーブやレスポンスの圧縮などの方法はフレームワークごとに違いますし ミドルウェアやプラグインを自分で入れて設定しないといけないです
これが結構面倒で 新しいフレームワークを使うときのハードルでもありました
ですが この辺を nginx 側に移すと Node.js 側でやらなくていいのでフレームワークの変更が楽になります
もちろん nginx を別のリバースプロキシのウェブサーバーに変えることはありえるのですが Node.js 側のフレームワークに比べるとめったにないです
フレームワークはコードを実装するのに大きく関連するところなので良さそうなものがあれば積極的に変えたいですが nginx の部分は必要な機能が動いてればそんなに変えたいものでもないです
一応 その他のもので使われてるものは Fastify のページで紹介されてるものだと HAProxy で他のところでは Caddy や lighttpd があるようでした
特にこれらに置き換えたい理由もないですし Node.js のフレームワークよりも次から次に出てくるツールではないと思います
また nginx 側を見ると最初こそ設定ファイルの見づらさはありますが 慣れると結構シンプルで読みやすいと思います
server や location みたいなブロックがあってそれぞれのコンテキストの中に書ける設定が決まっています
location は一つだけにマッチするので ネストすることで全体的な設定と個別の設定を書きます
設定は 「key value;」 形式の行からなるものでドキュメントに一覧があります
Node.js でのフレームワークごとのミドルウェア・プラグインの設定を探すよりもわかりやすいと思います
パフォーマンス的には劣るかもですが そもそもそんなに速度に困ってはないのでわずかに遅くなったくらいで影響はないので その他のメリットが多ければ別にいいかなというところです
それに静的ファイルを nginx で処理すれば その部分は Node.js より速くなるはずです
残る API の処理はもともと少し時間がかかることが多いところなので nginx が増えることによる時間程度は本当に誤差です
静的ファイルの処理を nginx に移すことでユーザー情報が参照できなくて URL を知ってれば誰でもアクセスできてしまいますが PHP だってずっとそれなわけですし 頑張れば画面が見れるくらいで実際のデータは API の認証が必要になるので別にいい気がします
あとこれは Node.js を直接公開してる場合が前提になってます
クラウドを使うみたいなことがあれば別にロードバランサーがあったりすると思うので そうなると Node.js 用の nginx とかはなくしてそっちに任せるでいいと思います
ロードバランサーでは静的ファイルのサーブはしてくれないかもですが クラウドなら CDN を使うことが多いそうですし
そもそもクラウドになると Lambda や Cloud Functions みたいなのがあってあまりサーバー自体を用意しないのかもしれませんけど
簡単に試してただけだと nginx で特に不満は無いと思ってましたが 少し使ってるとちょっとした不満点がありました
圧縮フォーマットの brotli に標準で対応していません
もちろん最近の zstd もです
古い gzip しか使えません
Node.js でも標準で brotli に対応してたので その点効率が落ちるのは残念かも
一応外部モジュールで追加はできるらしいのですが dnf などで簡単には導入できないみたいです
RHEL 系は epel でインストールできる風に書いてるページもあったのですが AlmaLinux9 ではパッケージが見つからなかったです
そんなに手間を掛けてまで入れたいわけではないので gzip で妥協していますが 最近は安定していてあまり新しい機能は追加されてないようなので将来性考えるとどうなのかなと思い始めました
不満点はもう一つあって JavaScript の mime type が application/javascript です
application/javascript はすでに廃止されていて text/javascript が正式です
https://datatracker.ietf.org/doc/html/rfc9239#name-iana-considerations
なのに普段使わないような古い形式になっていて 設定では application/javascript と書かないといけないです
つい text/javascript と書いて動かなかったことがありました
設定変えるなんてめったにないし ほとんどコピペなのですが やっぱりこういうのは気になるのですよね
これまでは nginx は無しで Node.js で全部やることにしていました
Node.js 内で全部やるほうが柔軟にできますし サーバーの追加は面倒です
間に追加のレイヤーが増えるほうが複雑化しますし 処理が増える以上パフォーマンス面でも劣りますし 単純に管理するものも増えます
設定ミスやバグなどでうまく動かないとかが出る可能性はありますし シンプル化できるものはシンプルにしておきたいという考えです
ただ Fastify では 前段に nginx などを置くことを推奨しています
たまには置いてみようかな ということで試してました
やってみると たしかに nginx を使ったほうが 圧縮とかキャッシュとか証明書や TLS バージョン等の HTTPS 関係やアクセスログなどを Node.js 側でやらなくていいので Node.js 側は楽になりました
静的ファイルも nginx に任せてしまうと Node.js は完全に API だけに専念できるので いつも入れてるライブラリ類も結構減りました
Node.js 側をシンプルにするという意味では良さそうです
Node.js で全部やると柔軟にできるといっても フレームワークの都合で うまくやりたいように設定できなくて遠回りに自分で制御してたりしたので これでいいような気もしてきました
静的ファイルのサーブとルートの処理の優先順とか SPA に対応させるとか フォルダごとにキャッシュ設定変えたりとか こういうことって結構面倒だったりしますし
それにフレームワークを変えるコストも減ります
Node.js ですべてやると静的ファイルのサーブやレスポンスの圧縮などの方法はフレームワークごとに違いますし ミドルウェアやプラグインを自分で入れて設定しないといけないです
これが結構面倒で 新しいフレームワークを使うときのハードルでもありました
ですが この辺を nginx 側に移すと Node.js 側でやらなくていいのでフレームワークの変更が楽になります
もちろん nginx を別のリバースプロキシのウェブサーバーに変えることはありえるのですが Node.js 側のフレームワークに比べるとめったにないです
フレームワークはコードを実装するのに大きく関連するところなので良さそうなものがあれば積極的に変えたいですが nginx の部分は必要な機能が動いてればそんなに変えたいものでもないです
一応 その他のもので使われてるものは Fastify のページで紹介されてるものだと HAProxy で他のところでは Caddy や lighttpd があるようでした
特にこれらに置き換えたい理由もないですし Node.js のフレームワークよりも次から次に出てくるツールではないと思います
また nginx 側を見ると最初こそ設定ファイルの見づらさはありますが 慣れると結構シンプルで読みやすいと思います
server や location みたいなブロックがあってそれぞれのコンテキストの中に書ける設定が決まっています
location は一つだけにマッチするので ネストすることで全体的な設定と個別の設定を書きます
設定は 「key value;」 形式の行からなるものでドキュメントに一覧があります
Node.js でのフレームワークごとのミドルウェア・プラグインの設定を探すよりもわかりやすいと思います
パフォーマンス的には劣るかもですが そもそもそんなに速度に困ってはないのでわずかに遅くなったくらいで影響はないので その他のメリットが多ければ別にいいかなというところです
それに静的ファイルを nginx で処理すれば その部分は Node.js より速くなるはずです
残る API の処理はもともと少し時間がかかることが多いところなので nginx が増えることによる時間程度は本当に誤差です
静的ファイルの処理を nginx に移すことでユーザー情報が参照できなくて URL を知ってれば誰でもアクセスできてしまいますが PHP だってずっとそれなわけですし 頑張れば画面が見れるくらいで実際のデータは API の認証が必要になるので別にいい気がします
あとこれは Node.js を直接公開してる場合が前提になってます
クラウドを使うみたいなことがあれば別にロードバランサーがあったりすると思うので そうなると Node.js 用の nginx とかはなくしてそっちに任せるでいいと思います
ロードバランサーでは静的ファイルのサーブはしてくれないかもですが クラウドなら CDN を使うことが多いそうですし
そもそもクラウドになると Lambda や Cloud Functions みたいなのがあってあまりサーバー自体を用意しないのかもしれませんけど
簡単に試してただけだと nginx で特に不満は無いと思ってましたが 少し使ってるとちょっとした不満点がありました
圧縮フォーマットの brotli に標準で対応していません
もちろん最近の zstd もです
古い gzip しか使えません
Node.js でも標準で brotli に対応してたので その点効率が落ちるのは残念かも
一応外部モジュールで追加はできるらしいのですが dnf などで簡単には導入できないみたいです
RHEL 系は epel でインストールできる風に書いてるページもあったのですが AlmaLinux9 ではパッケージが見つからなかったです
そんなに手間を掛けてまで入れたいわけではないので gzip で妥協していますが 最近は安定していてあまり新しい機能は追加されてないようなので将来性考えるとどうなのかなと思い始めました
不満点はもう一つあって JavaScript の mime type が application/javascript です
application/javascript はすでに廃止されていて text/javascript が正式です
https://datatracker.ietf.org/doc/html/rfc9239#name-iana-considerations
なのに普段使わないような古い形式になっていて 設定では application/javascript と書かないといけないです
つい text/javascript と書いて動かなかったことがありました
設定変えるなんてめったにないし ほとんどコピペなのですが やっぱりこういうのは気になるのですよね
Fastify の作りだとロガーはひとつです
fastify.log.info や request.log.info を使ってログするとすべて同じ場所への出力です
pino の設定で変えれはするものの レベルが◯◯以上はこのファイルにログする みたいな考え方なのでアクセスログとエラーログを分けるみたいな使い方ではなさそうです
いつもは アクセスログ / 操作系のログ / エラー・警告類 は別ファイルにしていましたが 1 ファイルに保存して 見るときにフィルタするほうがいいのでしょうか?
用途が違うものは分けたい派なのですけど
Fastify では decorate で標準のとは別に request.access_log みたいなものを作れますが pino との統合をやってくれないので推奨される方法ではない気がするのですよね
fastify.log.info や request.log.info を使ってログするとすべて同じ場所への出力です
pino の設定で変えれはするものの レベルが◯◯以上はこのファイルにログする みたいな考え方なのでアクセスログとエラーログを分けるみたいな使い方ではなさそうです
いつもは アクセスログ / 操作系のログ / エラー・警告類 は別ファイルにしていましたが 1 ファイルに保存して 見るときにフィルタするほうがいいのでしょうか?
用途が違うものは分けたい派なのですけど
Fastify では decorate で標準のとは別に request.access_log みたいなものを作れますが pino との統合をやってくれないので推奨される方法ではない気がするのですよね
一部のライブラリでは コールバック関数として渡した関数が受け取る引数の数で動作が変わります
こういうの
fn 側ではこういう感じで引数の数を見ています
これすごく分かりづらく感じるのでやめてほしいです
関数の引数の数ってあまりあてにならないです
デフォルト引数が設定された最初の引数より前の引数の数しかカウントされません
可変長引数は 0 になります
関数をラップして引数をそのまま渡して追加処理をするようなケースは可変長引数で受け取ることが多いのでそこで問題がおきます
使われどころはコールバック関数を受け取るかどうかで コールバックと Promise のどちらを使うか分岐するというのが多い気がします
コールバック関数を受け取らないなら Promise を返す関数で そうでないならコールバック関数を呼び出すことで終了を呼び出し元に伝えるので 両方に対応するならこういう方法になります
引数の数を見ないでとりあえず コールバックの関数を渡しておいて 返り値の Promise または渡した関数が呼び出されたかで判断でしてくれるといいのですが これの場合も使う側が async 関数にしてコールバック関数を使おうとするとうまく動かなかったりするのですよね
fn に渡すのが async 関数なので something2 を待たず resolve されてしまって ここで fn は next の呼び出しを待たずに終わったとみなしてしまいます
無理にまとめず別の関数に分けたほうがいいと思うのですけどね
他には なにかの機能を引数経由で提供していて 引数として受け取らないならその機能は使われないので準備するのをスキップするとかでしょうか
引数を受け取らないということは util を使うことはないので new Utility() をスキップできます
JavaScript では引数の数があってる必要はないので 関数の引数の数は気にせず扱ってほしいですね
こういうの
fn(() => {})
fn((a) => {})
fn((a, b) => {})
fn 側ではこういう感じで引数の数を見ています
const fn = (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
return callback()
}
case 1: {
console.log("1")
return callback(1)
}
case 2: {
console.log("2")
return callback(1, 2)
}
}
}
これすごく分かりづらく感じるのでやめてほしいです
関数の引数の数ってあまりあてにならないです
console.log(((a, b = 1, c) => {}).length)
// 1
console.log(((...args) => {}).length)
// 0
デフォルト引数が設定された最初の引数より前の引数の数しかカウントされません
可変長引数は 0 になります
関数をラップして引数をそのまま渡して追加処理をするようなケースは可変長引数で受け取ることが多いのでそこで問題がおきます
fn((a, b) => {})
// 1
// ↑を↓にすると
const wrap = org => (...args) => {
// beforeSomething()
const result = org(...args)
// afterSomething()
return result
}
fn(wrap((a, b) => {}))
// 0
使われどころはコールバック関数を受け取るかどうかで コールバックと Promise のどちらを使うか分岐するというのが多い気がします
コールバック関数を受け取らないなら Promise を返す関数で そうでないならコールバック関数を呼び出すことで終了を呼び出し元に伝えるので 両方に対応するならこういう方法になります
const fn = async (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
await callback()
break
}
case 1: {
console.log("1")
await new Promise(resolve => callback(resolve))
break
}
}
// after callback process
}
引数の数を見ないでとりあえず コールバックの関数を渡しておいて 返り値の Promise または渡した関数が呼び出されたかで判断でしてくれるといいのですが これの場合も使う側が async 関数にしてコールバック関数を使おうとするとうまく動かなかったりするのですよね
const fn = async (callback) => {
const { promise, resolve } = Promise.withResolvers()
const ret = callback(resolve)
const promises = (ret instanceof Promise) ? [ret, promise] : [promise]
await Promise.any(promises)
// after callback process
}
fn(async (next) => {
await something1()
something2(next)
})
fn に渡すのが async 関数なので something2 を待たず resolve されてしまって ここで fn は next の呼び出しを待たずに終わったとみなしてしまいます
無理にまとめず別の関数に分けたほうがいいと思うのですけどね
他には なにかの機能を引数経由で提供していて 引数として受け取らないならその機能は使われないので準備するのをスキップするとかでしょうか
const fn = (callback) => {
switch (callback.length) {
case 0: {
console.log("0")
return callback()
}
case 1: {
console.log("1")
const util = new Utility()
return callback(util)
}
}
}
引数を受け取らないということは util を使うことはないので new Utility() をスキップできます
JavaScript では引数の数があってる必要はないので 関数の引数の数は気にせず扱ってほしいですね
特に役立たない話
yarn init で berry の初期化をするとき git でリポジトリが自動で作られます
git が入っていないとエラーが起きます
という感じ
git が入ってない環境で使うこともありますし git の初期化はスキップしたいです
ですが オプションに git の初期化をスキップする設定が見当たらないです
しかし .git フォルダがすでにあると初期化処理はスキップされるようになっています
https://github.com/yarnpkg/berry/blob/%40yarnpkg/cli/4.1.1/packages/plugin-init/sources/commands/init.ts#L252
つまり
とすればエラーはなくなります
ただ上のリンクのソースコードを見ても分かる通り git の初期化処理は最後の処理です
エラーで途中終了しても init の処理は終わってるのでエラーを消しても特に何も変わりません
ログにエラーを出したくないということがあれば使えます
エラーが無い場合は Done の行までの出力になります
yarn init で berry の初期化をするとき git でリポジトリが自動で作られます
git が入っていないとエラーが起きます
[root@60303402bf2f foo]# yarn init -2
➤ YN0000: You don't seem to have Corepack enabled; we'll have to rely on yarnPath instead
➤ YN0000: Downloading https://repo.yarnpkg.com/4.1.1/packages/yarnpkg-cli/bin/yarn.js
➤ YN0000: Saving the new release in .yarn/releases/yarn-4.1.1.cjs
➤ YN0000: Done with warnings in 0s 140ms
➤ YN0000: · Yarn 4.1.1
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed
➤ YN0000: · Done in 0s 127ms
Internal Error: Process git failed to spawn
at ChildProcess.<anonymous> (/foo/.yarn/releases/yarn-4.1.1.cjs:149:5568)
at ChildProcess.emit (node:events:518:28)
at ChildProcess._handle.onexit (node:internal/child_process:292:12)
at onErrorNT (node:internal/child_process:484:16)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
error Command failed.
Exit code: 1
Command: /usr/bin/node
Arguments: /usr/local/bin/yarn init -2
Directory: /foo
Output:
info Visit https://yarnpkg.com/en/docs/cli/init for documentation about this command.
という感じ
git が入ってない環境で使うこともありますし git の初期化はスキップしたいです
ですが オプションに git の初期化をスキップする設定が見当たらないです
しかし .git フォルダがすでにあると初期化処理はスキップされるようになっています
https://github.com/yarnpkg/berry/blob/%40yarnpkg/cli/4.1.1/packages/plugin-init/sources/commands/init.ts#L252
つまり
mkdir .git
yarn init -2
rmdir .git
とすればエラーはなくなります
ただ上のリンクのソースコードを見ても分かる通り git の初期化処理は最後の処理です
エラーで途中終了しても init の処理は終わってるのでエラーを消しても特に何も変わりません
ログにエラーを出したくないということがあれば使えます
エラーが無い場合は Done の行までの出力になります
pino の redact を見てると 「〇」 が使われてた
https://github.com/davidmarkclements/fast-redact/blob/v3.4.0/lib/validator.js
じゃあ〇を使うとエラーになる?と 思い付きで試してみました
pino と redact の使い方としてはこういうの
ログするオブジェクトのパスを複数指定できて 指定したプロパティが伏せられます
〇 を入れてみると
確かにエラーになってます
ログをしなくてもインスタンスの作成時点でエラーです
少し特殊な
になってるのは記号の対処のためで 内部でプロパティをどう処理してるかというと
でコードを作ってそれを実行しています
eval みたいなものです
expr が .prop みたいになってます
foo.bar は有効ですが foo.1 や foo.! は無効です
JavaScript として有効なアクセス方法になるように [1] や ["!"] みたいにしないといけないです
そのため少し特殊な書き方になってます
記号などでも ["!"] の書き方にすると動作します
〇 の記号はいくつかあって fast-redact に使われてるのは一番上のものです
〇 U+3007
◯ U+25EF
○ U+25CB
他の 〇 ならエラーになりません
https://github.com/davidmarkclements/fast-redact/blob/v3.4.0/lib/validator.js
じゃあ〇を使うとエラーになる?と 思い付きで試してみました
pino と redact の使い方としてはこういうの
> require("pino")({ redact: ["key"] }).info({ key: "foo" })
{"level":30,"time":1710331939803,"pid":54,"hostname":"67cfbc893e4a","key":"[Redacted]"}
> require("pino")({ redact: ["あ"] }).info({ あ: "foo" })
{"level":30,"time":1710331978251,"pid":54,"hostname":"67cfbc893e4a","あ":"[Redacted]"}
ログするオブジェクトのパスを複数指定できて 指定したプロパティが伏せられます
〇 を入れてみると
> require("pino")({ redact: [`["〇"]`] })
Uncaught Error: pino – redact paths array contains an invalid path (["〇"])
at /mnt/x8uc1/node_modules/fast-redact/lib/validator.js:29:15
at Array.forEach (<anonymous>)
at validate (/mnt/x8uc1/node_modules/fast-redact/lib/validator.js:12:11)
at handle (/mnt/x8uc1/node_modules/pino/lib/redaction.js:107:5)
at redaction (/mnt/x8uc1/node_modules/pino/lib/redaction.js:16:29)
at pino (/mnt/x8uc1/node_modules/pino/pino.js:129:33)
確かにエラーになってます
ログをしなくてもインスタンスの作成時点でエラーです
少し特殊な
[`["〇"]`]
になってるのは記号の対処のためで 内部でプロパティをどう処理してるかというと
o${expr}
でコードを作ってそれを実行しています
eval みたいなものです
expr が .prop みたいになってます
foo.bar は有効ですが foo.1 や foo.! は無効です
JavaScript として有効なアクセス方法になるように [1] や ["!"] みたいにしないといけないです
そのため少し特殊な書き方になってます
記号などでも ["!"] の書き方にすると動作します
> require("pino")({ redact: ["!"] }).info({ "!": "a" })
Uncaught Error: pino – redact paths array contains an invalid path (!)
(略)
> require("pino")({ redact: [`["!"]`] }).info({ "!": "a" })
{"level":30,"time":1710352889569,"pid":350,"hostname":"8f9aa7f09ea2","!":"[Redacted]"}
〇 の記号はいくつかあって fast-redact に使われてるのは一番上のものです
〇 U+3007
◯ U+25EF
○ U+25CB
他の 〇 ならエラーになりません
> const fn = k => {
... console.log(k, k.charCodeAt().toString(16))
... require("pino")({ redact: [`["${k}"]`] }).info({ [k]: "a" })
... }
> fn("〇")
〇 3007
Uncaught Error: pino – redact paths array contains an invalid path (["〇"])
(略)
> fn("◯")
◯ 25ef
{"level":30,"time":1710416813795,"pid":3966,"hostname":"67cfbc893e4a","◯":"[Redacted]"}
> fn("○")
○ 25cb
{"level":30,"time":1710416820183,"pid":3966,"hostname":"67cfbc893e4a","○":"[Redacted]"}
Node.js を ESM にしてから不便に感じてる部分はやっぱりファイルパスの解決部分です
CJS のときはこんな感じで書けた部分ですが
[/tmp/a.js]
この長さになります
[/tmp/b.js]
Node.js 20 なら import.meta に resolve があって __dirname を作らなくてもそのファイルからの相対パスでフルパスを取得できます
でも file:// から始まる URL 形式なので最後に fileURLToPath を通してローカルのパス形式にしないといけないです
[/tmp/c.js]
Node.js 21 なら import.meta.dirname があるので CJS と同じようなことができます
🔗 Node.js 21.2 で ESM に CJS の __dirname と __filename 相当の機能が追加された
[/tmp/d.js]
でも現在の LTS は 20 です
21 に機能追加されて 3 ヶ月以上経っても 20 にバックポートされないのであまり期待できなそうです
使えるのは 22 の LTS からになるかもしれません
それに CJS と近い感じで書けるだけで __dirname が import.meta.dirname になって少し長いです
fs が URL 形式のパスもサポートしてくれるともっと楽なんですけどね
それなら同じフォルダのファイルを読み取るときはこれだけで済みます
そういう話がないのか探してみたのですが あまり積極的ではないようで放置されて issue がクローズされてました
https://github.com/nodejs/node/issues/48994
単に中で fileURLToPath を通すだけで file://foo/bar みたいなものを入れてもエラーにするでいいと思うのですけど
CJS のときはこんな感じで書けた部分ですが
[/tmp/a.js]
const path = require("path")
const file_path = path.join(__dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt
この長さになります
[/tmp/b.js]
import { fileURLToPath } from "node:url"
import path from "node:path"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const file_path = path.join(__dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt
Node.js 20 なら import.meta に resolve があって __dirname を作らなくてもそのファイルからの相対パスでフルパスを取得できます
でも file:// から始まる URL 形式なので最後に fileURLToPath を通してローカルのパス形式にしないといけないです
[/tmp/c.js]
import { fileURLToPath } from "node:url"
const file_url = import.meta.resolve("./file.txt")
console.log(file_url)
// file:///tmp/file.txt
const file_path = fileURLToPath(file_url)
console.log(file_path)
// /tmp/file.txt
Node.js 21 なら import.meta.dirname があるので CJS と同じようなことができます
🔗 Node.js 21.2 で ESM に CJS の __dirname と __filename 相当の機能が追加された
[/tmp/d.js]
import path from "node:path"
const file_path = path.join(import.meta.dirname, "./file.txt")
console.log(file_path)
// /tmp/file.txt
でも現在の LTS は 20 です
21 に機能追加されて 3 ヶ月以上経っても 20 にバックポートされないのであまり期待できなそうです
使えるのは 22 の LTS からになるかもしれません
それに CJS と近い感じで書けるだけで __dirname が import.meta.dirname になって少し長いです
fs が URL 形式のパスもサポートしてくれるともっと楽なんですけどね
それなら同じフォルダのファイルを読み取るときはこれだけで済みます
fs.readFileSync(import.meta.resolve("./file.txt"))
そういう話がないのか探してみたのですが あまり積極的ではないようで放置されて issue がクローズされてました
https://github.com/nodejs/node/issues/48994
単に中で fileURLToPath を通すだけで file://foo/bar みたいなものを入れてもエラーにするでいいと思うのですけど
うまく動かないところがあって 原因を見つけるのに苦戦しました
たまにしか使わない処理なので最初の使用時に初期化処理を行うようにしてるものです
イメージ
初期化処理に非同期処理が含まれるので初期化処理全体が非同期処理になっています
2 回目の呼び出しが 1 回目の直後だと インスタンスはあるものの初期化処理が完全に終わってないので instance.run の実行はできるのに何も行われないみたいな動きになってました
インスタンスの初期化が終わらないと 内部のリスナが設定されていなくてイベントを起こしても何も起きないという状況でした
ここが undefined のプロパティ参照や非関数の実行など実行時にエラーになってくれていれば簡単に原因がわかったのですけどね
インスタンス側に ready プロパティを用意して初期化後に解決する Promise を入れておくなどの対処が必要でした
ただ この最初の呼び出しで初期化する方法だと instance.run が同期的なのに execute の処理が非同期処理になってしまうのですよね
setupInstance が同期処理だとそもそも発生しない問題ですし あちこちが非同期処理になると不便なところも多いです
たまにしか使わない処理なので最初の使用時に初期化処理を行うようにしてるものです
イメージ
const execute = async (args) => {
if (!instance) {
await setupInstance()
}
return instance.run(args)
}
初期化処理に非同期処理が含まれるので初期化処理全体が非同期処理になっています
2 回目の呼び出しが 1 回目の直後だと インスタンスはあるものの初期化処理が完全に終わってないので instance.run の実行はできるのに何も行われないみたいな動きになってました
インスタンスの初期化が終わらないと 内部のリスナが設定されていなくてイベントを起こしても何も起きないという状況でした
ここが undefined のプロパティ参照や非関数の実行など実行時にエラーになってくれていれば簡単に原因がわかったのですけどね
インスタンス側に ready プロパティを用意して初期化後に解決する Promise を入れておくなどの対処が必要でした
const execute = async (args) => {
if (!instance) {
await setupInstance()
}
await instance.ready
return instance.run(args)
}
ただ この最初の呼び出しで初期化する方法だと instance.run が同期的なのに execute の処理が非同期処理になってしまうのですよね
setupInstance が同期処理だとそもそも発生しない問題ですし あちこちが非同期処理になると不便なところも多いです
こんな HTML があって
.article の中にだけスタイルを当てたいです
ただしそのスタイルは自分で書くものではなくユーザー入力です
なのでセレクタで .article の中だけが対象になっている保証はないです
最近は CSS をネストできるので ユーザー CSS の構文が正しいことを確認したあとに
みたいにすればいいかなと思ったりもしました
ですが
とすれば body にスタイルを当てられます
ShadowDOM を使ったほうがいいのかなと思ったものの slot の中にはスタイルが当たりません
slot を使わず ShadowDOM の中に .article の子要素を持ってきてしまえばスタイルは当たりますが あまり良いやり方に思えないんですよね
(追記) (コメントありがとうございます)
@scope でもネストと同じで突破できそうと思ってましたが いろいろ試してみたところ大丈夫そうでした
& はスコープ開始のセレクタを書くのと同じらしいですが そもそもスコープ内しか対象にしない機能なので body などの外側の要素を選択しても効果がないです
あとは } が多くて @scope を抜けてしまうみたいのを許可しなければ大丈夫そうです
ネストのときは すぐにダメそうとわかって あまり考えてなかったですが CSS って HTML の innerHTML みたいな感じで CSSStyleSheet から完全な CSS 文字列を簡単に作れないんですよね
標準機能だけで構文エラーを修正したものを作れるといいのですけど
この辺はライブラリに任せたほうがいいかもしれません
続き → CSSStyleSheet から CSS の文字列を取り出したい
<h1>H1</h1>
<p>p</p>
<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>
.article の中にだけスタイルを当てたいです
ただしそのスタイルは自分で書くものではなくユーザー入力です
なのでセレクタで .article の中だけが対象になっている保証はないです
最近は CSS をネストできるので ユーザー CSS の構文が正しいことを確認したあとに
const style = `
.article {
${original_style}
}
`
みたいにすればいいかなと思ったりもしました
ですが
.article {
body:has(&) {
background: black;
}
}
とすれば body にスタイルを当てられます
ShadowDOM を使ったほうがいいのかなと思ったものの slot の中にはスタイルが当たりません
<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)
const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.innerHTML = `<slot/>`
</script>
<h1>H1</h1>
<p>p</p>
<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>
slot を使わず ShadowDOM の中に .article の子要素を持ってきてしまえばスタイルは当たりますが あまり良いやり方に思えないんですよね
<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)
const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.replaceChildren(...article.childNodes)
</script>
<h1>H1</h1>
<p>p</p>
<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>(追記) (コメントありがとうございます)
@scope でもネストと同じで突破できそうと思ってましたが いろいろ試してみたところ大丈夫そうでした
& はスコープ開始のセレクタを書くのと同じらしいですが そもそもスコープ内しか対象にしない機能なので body などの外側の要素を選択しても効果がないです
あとは } が多くて @scope を抜けてしまうみたいのを許可しなければ大丈夫そうです
ネストのときは すぐにダメそうとわかって あまり考えてなかったですが CSS って HTML の innerHTML みたいな感じで CSSStyleSheet から完全な CSS 文字列を簡単に作れないんですよね
標準機能だけで構文エラーを修正したものを作れるといいのですけど
この辺はライブラリに任せたほうがいいかもしれません
続き → CSSStyleSheet から CSS の文字列を取り出したい
iPad で Google Sheets のリンクを開いたら 「お使いのブラウザのバージョンはサポートが終了しました」 ってメッセージが出てきた
アプリ版を使えってことかもだけど Google でも Safari のサポートしないんだなーって思った
競合とはいえ サービスのユーザーを増やす機会なのに
Google Sheets みたいな複雑な機能になるとやっぱり Safari の対応はかなりつらいものなのかな
最近は以前より Safari で動かないが減ってきたように思うけど
アプリ版を使えってことかもだけど Google でも Safari のサポートしないんだなーって思った
競合とはいえ サービスのユーザーを増やす機会なのに
Google Sheets みたいな複雑な機能になるとやっぱり Safari の対応はかなりつらいものなのかな
最近は以前より Safari で動かないが減ってきたように思うけど
フォルダ構造がこうなってるプロジェクトフォルダがあります
ワークスぺース機能を使ってないので 個別に yarn install して node_modules が複数あります
ワークスペースを使ったほうがいいかなと思ったものの現状のメリットもあるので迷ってます
ワークスペースを使ったほうがいいところは node_modules がまとまってムダがないことです
ワークスペースを使ってないと共通のパッケージがあるとき各 node_modules に同じパッケージがインストールされます
また yarn install を個別にする必要もないです
パッケージ間の関係でも あるパッケージから別パッケージを参照する場合に楽です
逆にワークスペースにすると node_modules が 1 つになるので yarn install の処理が重たくなります
共通のパッケージがあまりない場合は パッケージ数が大きく増えますし デメリットが大きいです
あと ワークスペースにしたときにサーバー上での yarn install で特定パッケージの依存関係のみインストールってできるのでしょうか
ウェブサーバーや別の常駐アプリと共通部分のパッケージはインストールが必要ですけど フロント側で使うパッケージはいらないです
入ることで特別問題はないのですが フロント系はパッケージ数が増えて重くなるので不要なサーバーでまで入れたくないなと思います
- project_root/
- package.json
- node_modules/...
- package1/
- package.json
- node_modules/...
- package2/
- package.json
- node_modules/...
ワークスぺース機能を使ってないので 個別に yarn install して node_modules が複数あります
ワークスペースを使ったほうがいいかなと思ったものの現状のメリットもあるので迷ってます
ワークスペースを使ったほうがいいところは node_modules がまとまってムダがないことです
ワークスペースを使ってないと共通のパッケージがあるとき各 node_modules に同じパッケージがインストールされます
また yarn install を個別にする必要もないです
パッケージ間の関係でも あるパッケージから別パッケージを参照する場合に楽です
逆にワークスペースにすると node_modules が 1 つになるので yarn install の処理が重たくなります
共通のパッケージがあまりない場合は パッケージ数が大きく増えますし デメリットが大きいです
あと ワークスペースにしたときにサーバー上での yarn install で特定パッケージの依存関係のみインストールってできるのでしょうか
ウェブサーバーや別の常駐アプリと共通部分のパッケージはインストールが必要ですけど フロント側で使うパッケージはいらないです
入ることで特別問題はないのですが フロント系はパッケージ数が増えて重くなるので不要なサーバーでまで入れたくないなと思います
値とそれを更新するための関数を作る時よくやるやつ
変数と関数をフラットにおいてる
クラス好きな人ならクラス化しそう
一回限りなので即時インスタンス化して
こうすると append を渡すときに this のコンテキストが消えるので bind やアロー関数でラップが必要になる
使う側で気にしなくていいようにプロトタイプじゃなくてインスタンス自身に関数を入れる
これならもうクラスの必要性がないのでただのオブジェクトにして
const values = []
const append = (data) => {
if (data.type === 0) {
values.push(data.value)
}
}
変数と関数をフラットにおいてる
クラス好きな人ならクラス化しそう
class X {
values = []
append(data) {
if (data.type === 0) {
this.values.push(data.value)
}
}
}
const x = new X()
一回限りなので即時インスタンス化して
const x = new class {
values = []
append(data) {
if (data.type === 0) {
this.values.push(data.value)
}
}
}
こうすると append を渡すときに this のコンテキストが消えるので bind やアロー関数でラップが必要になる
使う側で気にしなくていいようにプロトタイプじゃなくてインスタンス自身に関数を入れる
const x = new class {
values = []
append = (data) => {
if (data.type === 0) {
this.values.push(data.value)
}
}
}
これならもうクラスの必要性がないのでただのオブジェクトにして
const x = {
values: [],
append: (data) => {
if (data.type === 0) {
x.values.push(data.value)
}
},
}
ネットで見かけた記事でシンボルの使い方について プライベートプロパティとして使うみたいなのがありました
これで sym をエクスポートせず X だけエクスポートすると x[sym] ができないから外部から直接アクセスできないってやつですね
使い方としては別にいいと思うのですが こうすることで外部からは完全にアクセス不可能で更新できないみたいなことが書かれてました
マイナーですが Object.getOwnPropertySymbols でシンボルを取得することができます
それ以外の Object.keys や for-in ではシンボルは取ってこれないです
困ることがあるとすれば 作る側が Symbol に説明を付けてない場合です
Symbol 関数の引数に文字列を入れていれば description プロパティで参照できます
それがないと複数のシンボルがあるとき何番目がどのプロパティなのか分かりづらいです
また Object.getOwnPropertyDescriptors ではシンボルの情報も取れます
しかし キーがシンボルのオブジェクトを受け取るので結局これだけじゃアクセスできなかったりします
最近では プライベートプロパティを使ってもデバッグ用途だと不便は減りましたが 実行時にどうやってもアクセスできない不便さがあるので これくらいのゆるさのものが良いですね
const sym = Symbol()
class X {
[sym] = 1
print() {
console.log(this[sym])
}
}
const x = new X()
console.log(x[sym])
// 1
x.print()
// 1
これで sym をエクスポートせず X だけエクスポートすると x[sym] ができないから外部から直接アクセスできないってやつですね
使い方としては別にいいと思うのですが こうすることで外部からは完全にアクセス不可能で更新できないみたいなことが書かれてました
マイナーですが Object.getOwnPropertySymbols でシンボルを取得することができます
それ以外の Object.keys や for-in ではシンボルは取ってこれないです
console.log(Object.keys(x))
// []
for (const key in x) {
console.log(key)
}
// (no output)
const syms = Object.getOwnPropertySymbols(x)
console.log(x[syms[0]])
// 1
x[syms[0]] = 10
x.print()
// 10
困ることがあるとすれば 作る側が Symbol に説明を付けてない場合です
Symbol 関数の引数に文字列を入れていれば description プロパティで参照できます
それがないと複数のシンボルがあるとき何番目がどのプロパティなのか分かりづらいです
また Object.getOwnPropertyDescriptors ではシンボルの情報も取れます
しかし キーがシンボルのオブジェクトを受け取るので結局これだけじゃアクセスできなかったりします
最近では プライベートプロパティを使ってもデバッグ用途だと不便は減りましたが 実行時にどうやってもアクセスできない不便さがあるので これくらいのゆるさのものが良いですね
久々に V8 の --harmony-xxx のフラグを使ったのですが 今って harmony フラグってどれくらいあるんでしょうか
ググって出てくるものって過去の時点のものばかりなので参考にできないです
Node.js だと --v8-options で V8 のオプション一覧が出せるので ここから harmony を含むものを取り出します
結構多めですね
ただ array-grouping や change-array-by-copy など すでにリリース済み機能も入ってるようです
出たばかりはまだ安定してないかもなので無効にするためなんでしょうか
デフォルトで有効かどうかは default のところが --no- ではじまるかでわかります
--no- で始まってれば無効です
in progress / experimental とは一致してないみたいです
V8 のソースコード的にはこの辺で定義されてるようでした
https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L247
https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L292
ググって出てくるものって過去の時点のものばかりなので参考にできないです
Node.js だと --v8-options で V8 のオプション一覧が出せるので ここから harmony を含むものを取り出します
root@c3872dcf640f:/# node -v
v21.6.0
root@c3872dcf640f:/# node -p process.versions.v8
11.8.172.17-node.19
root@c3872dcf640f:/# node --v8-options | grep harmony
--harmony (enable all completed harmony features)
type: bool default: --no-harmony
--harmony-shipping (enable all shipped harmony features)
type: bool default: --harmony-shipping
--harmony-import-attributes (enable "harmony import attributes" (in progress / experimental))
type: bool default: --harmony-import-attributes
--harmony-weak-refs-with-cleanup-some (enable "harmony weak references with FinalizationRegistry.prototype.cleanupSome" (in progress / experimental))
type: bool default: --no-harmony-weak-refs-with-cleanup-some
--harmony-temporal (enable "Temporal" (in progress / experimental))
type: bool default: --no-harmony-temporal
--harmony-shadow-realm (enable "harmony ShadowRealm" (in progress / experimental))
type: bool default: --no-harmony-shadow-realm
--harmony-struct (enable "harmony structs, shared structs, and shared arrays" (in progress / experimental))
type: bool default: --no-harmony-struct
--harmony-array-from-async (enable "harmony Array.fromAsync" (in progress / experimental))
type: bool default: --no-harmony-array-from-async
--harmony-intl-best-fit-matcher (enable "Intl BestFitMatcher" (in progress / experimental))
type: bool default: --no-harmony-intl-best-fit-matcher
--harmony-remove-intl-locale-info-getters (enable "Remove Obsoleted Intl Locale Info getters" (in progress / experimental))
type: bool default: --no-harmony-remove-intl-locale-info-getters
--harmony-intl-locale-info-func (enable "Intl Locale Info API as functions" (in progress / experimental))
type: bool default: --no-harmony-intl-locale-info-func
--harmony-intl-duration-format (enable "Intl DurationFormat API" (in progress / experimental))
type: bool default: --no-harmony-intl-duration-format
--harmony-set-methods (enable "harmony Set Methods")
type: bool default: --no-harmony-set-methods
--harmony-iterator-helpers (enable "JavaScript iterator helpers")
type: bool default: --no-harmony-iterator-helpers
--harmony-import-assertions (enable "harmony import assertions")
type: bool default: --harmony-import-assertions
--harmony-change-array-by-copy (enable "harmony change-Array-by-copy")
type: bool default: --harmony-change-array-by-copy
--harmony-rab-gsab (enable "harmony ResizableArrayBuffer / GrowableSharedArrayBuffer")
type: bool default: --harmony-rab-gsab
--harmony-regexp-unicode-sets (enable "harmony RegExp Unicode Sets")
type: bool default: --harmony-regexp-unicode-sets
--harmony-json-parse-with-source (enable "harmony json parse with source")
type: bool default: --harmony-json-parse-with-source
--harmony-rab-gsab-transfer (enable "harmony ArrayBuffer.transfer")
type: bool default: --harmony-rab-gsab-transfer
--harmony-array-grouping (enable "harmony array grouping")
type: bool default: --harmony-array-grouping
結構多めですね
ただ array-grouping や change-array-by-copy など すでにリリース済み機能も入ってるようです
出たばかりはまだ安定してないかもなので無効にするためなんでしょうか
デフォルトで有効かどうかは default のところが --no- ではじまるかでわかります
--no- で始まってれば無効です
in progress / experimental とは一致してないみたいです
V8 のソースコード的にはこの辺で定義されてるようでした
https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L247
https://github.com/v8/v8/blob/12.2.280/src/flags/flag-definitions.h#L292
テスト用にその場限りで適当にデータを増やしたいことがあります
みたいなのがあって配列の要素数を 30 くらいにしたいです
実際はもっと長いので範囲選択のコピペもちょっとめんどうだったりします
みたいなことがしたいですが配列には repeat メソッドはありません
サクッとかける方法でメソッドで要素数を増やしたいです
ということで使ってる flatMap
1, 2, 3 の繰り返しじゃなくて 1 が続いた後で 2 が続いて 3 が続くことになるのと 全部参照は同じという欠点はあるのですが 数が増えれば中身は気にしない場所ではこれでもよかったりします
something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
],
)
みたいなのがあって配列の要素数を 30 くらいにしたいです
実際はもっと長いので範囲選択のコピペもちょっとめんどうだったりします
something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
].repeat(10),
)
みたいなことがしたいですが配列には repeat メソッドはありません
サクッとかける方法でメソッドで要素数を増やしたいです
ということで使ってる flatMap
something(
value,
[
{ a: 1 },
{ a: 2 },
{ a: 3 },
].flatMap(x => Array(10).fill(x)),
)
1, 2, 3 の繰り返しじゃなくて 1 が続いた後で 2 が続いて 3 が続くことになるのと 全部参照は同じという欠点はあるのですが 数が増えれば中身は気にしない場所ではこれでもよかったりします
Iterator Helpers はじめウェブ互換に影響してる変更を見ていてふと思ったけど引数の追加でも壊れそうですよね
JavaScript だと map や filter 等の関数ではコールバック関数の 2 つめに index が 3 つめには配列自身が渡されます
parseInt を map に使うとおかしくなるのは有名です
これがあるのと this が壊れることがあるのでアロー関数を使うことが多かったりしますが 直接関数を入れるケースもありえます
多いのだと Boolean でフィルタです
他にも btoa など受け取る引数が 1 つのものは直接コールバック関数に渡したりします
こういった関数に引数が追加されると 意図せず 2 つめ以降の引数を渡してしまって動作が変わります
引数の追加って互換性あるように見えますが JavaScript みたいに引数の数が一致してなくても動く言語だとそうとも言えなそうですね
JavaScript だと map や filter 等の関数ではコールバック関数の 2 つめに index が 3 つめには配列自身が渡されます
parseInt を map に使うとおかしくなるのは有名です
[1, 1, 1].map(parseInt)
// [1, NaN, 1]
これがあるのと this が壊れることがあるのでアロー関数を使うことが多かったりしますが 直接関数を入れるケースもありえます
多いのだと Boolean でフィルタです
[{a: 1}, null, {a: 2}].filter(Boolean)
// [{a: 1}, {a: 2}]
他にも btoa など受け取る引数が 1 つのものは直接コールバック関数に渡したりします
["abc", "xyz"].map(btoa)
// ['YWJj', 'eHl6']
こういった関数に引数が追加されると 意図せず 2 つめ以降の引数を渡してしまって動作が変わります
引数の追加って互換性あるように見えますが JavaScript みたいに引数の数が一致してなくても動く言語だとそうとも言えなそうですね
Windows Sandbox を使いたいことがよくあるのですが その時使ってる PC が Pro じゃないこともあります
公式ドキュメントでは Home エディションはサポートしてないと書かれています
ただ ググると Home エディションでも有効化するという bat ファイルが色々出てきます
サイトはいくつも出てきますが bat ファイルの中身は同じみたいです
怪しげなファイルをインストールするものだったり 海賊版的な方法だったりするのかと不安もあるので中身を見てみました
長い割にほとんどが管理者権限で動かすための部分だったりで実質は以下の部分だけでした
パッケージの追加もパッケージ名はローカルにあるものから作ってますし外部からの取得はしてなさそうです
これだけで動くなら正規の方法のようです
ただ問題起きたら影響が大きそうなので事前に壊れてもいい PC で試したいので実行はまた今度にします
ちなみに 1, 2 行目でやってることは
C:\Windows\servicing\Packages\ 内の Containers を含む .mum ファイルすべてに対して
を実行しています
*** のところに .mum ファイルのフルパスが入ります
一旦ファイルに出力してますが 特別な意味はなさそうです
findstr を使ってますが findstr は正規表現で検索するコマンドで 検索条件が「.」なので全件に一致します
空行は無視して 1 件ごと取得するために使ってそうです
作ったファイルもすぐに消しています
やってることは PowerShell のこれと同じはず
最終的に有効化するのが Containers-DisposableClientVM なら add-package するのは Containers を含む全てではなく Containers-DisposableClientVM を含むものだけでも良さそうに見えますが 他も必要になるのでしょうか
公式ドキュメントでは Home エディションはサポートしてないと書かれています
ただ ググると Home エディションでも有効化するという bat ファイルが色々出てきます
サイトはいくつも出てきますが bat ファイルの中身は同じみたいです
怪しげなファイルをインストールするものだったり 海賊版的な方法だったりするのかと不安もあるので中身を見てみました
長い割にほとんどが管理者権限で動かすための部分だったりで実質は以下の部分だけでした
dir /b %SystemRoot%\servicing\Packages\*Containers*.mum >sandbox.txt
for /f %%i in ('findstr /i . sandbox.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"
Dism /online /enable-feature /featurename:Containers-DisposableClientVM /LimitAccess /ALL
パッケージの追加もパッケージ名はローカルにあるものから作ってますし外部からの取得はしてなさそうです
これだけで動くなら正規の方法のようです
ただ問題起きたら影響が大きそうなので事前に壊れてもいい PC で試したいので実行はまた今度にします
ちなみに 1, 2 行目でやってることは
C:\Windows\servicing\Packages\ 内の Containers を含む .mum ファイルすべてに対して
dism /online /norestart /add-package:***
を実行しています
*** のところに .mum ファイルのフルパスが入ります
一旦ファイルに出力してますが 特別な意味はなさそうです
findstr を使ってますが findstr は正規表現で検索するコマンドで 検索条件が「.」なので全件に一致します
空行は無視して 1 件ごと取得するために使ってそうです
作ったファイルもすぐに消しています
やってることは PowerShell のこれと同じはず
$items = dir C:\Windows\servicing\Packages\*Containers*.mum
foreach ($i in $items) {
dism /online /norestart /add-package:$i.FullName
}
dism /online /enable-feature /featurename:Containers-DisposableClientVM /LimitAccess /ALL
最終的に有効化するのが Containers-DisposableClientVM なら add-package するのは Containers を含む全てではなく Containers-DisposableClientVM を含むものだけでも良さそうに見えますが 他も必要になるのでしょうか
> dir C:\Windows\servicing\Packages\*Containers-DisposableClientVM*.mum | select name
Name
----
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.1.mum
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~ja-JP~10.0.19041.1.mum
Containers-DisposableClientVM-merged-Package~31bf3856ad364e35~amd64~~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~en-US~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~ja-JP~10.0.19041.3636.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~~10.0.19041.3693.mum
Containers-DisposableClientVM-Package~31bf3856ad364e35~amd64~~10.0.19041.3803.mum
なんとなく Deno のドキュメントを眺めてると思ったよりも標準ライブラリが充実していました
https://deno.land/std@0.209.0?doc
CSV/JSONC/YAML/TOML などのファイル形式を扱えます
HTML のエスケープや正規表現のエスケープ機能があります
日付のフォーマット機能があります(足し算や月末取得などはなくシンプルな機能のみみたい)
この辺は普段から JavaScript のデフォルト機能にあってほしいと思うものです
Deno の場合は標準ライブラリも URL を指定してダウンロードするわけですが 一応標準ライブラリという扱いで入ってるのは良いところだと思います
Deno が npm サポートや Node.js 互換に方針転換したくらいから興味を失ってましたが Node.js 用コードがほぼそのままで動くわけですし 便利機能が多いなら Deno の方を使うでもいいのかなと思いました
Node.js の新機能で Deno にあったから追加したというのも割りと見ます
Deno のほうが先に機能追加されてると言えます
TypeScript 関連でも Deno の対応が早いと感じてます
JavaScript に新規追加された機能で TypeScript 公式に型定義を提供してないので使うと型エラーになるというものがありましたが Deno では独自に対応してたりしました
動かすために Deno のコードから型定義をコピペしてきたこともありました
懸念は std の機能にまだ Unstable が多いところです
以前も全然安定してないし まだいいかと思って詳細は見なかった覚えがあります
でも結構経ってもまだ Unstable ですし 長期的に Unstable のままになってそうです
日付のフォーマットとか HTML のエスケープなどは API や動作が変わりそうなものでもないですし バージョンを固定してれば勝手に動かなくなってるわけでもないので 別にいいかなというところです
https://deno.land/std@0.209.0?doc
CSV/JSONC/YAML/TOML などのファイル形式を扱えます
HTML のエスケープや正規表現のエスケープ機能があります
日付のフォーマット機能があります(足し算や月末取得などはなくシンプルな機能のみみたい)
この辺は普段から JavaScript のデフォルト機能にあってほしいと思うものです
Deno の場合は標準ライブラリも URL を指定してダウンロードするわけですが 一応標準ライブラリという扱いで入ってるのは良いところだと思います
Deno が npm サポートや Node.js 互換に方針転換したくらいから興味を失ってましたが Node.js 用コードがほぼそのままで動くわけですし 便利機能が多いなら Deno の方を使うでもいいのかなと思いました
Node.js の新機能で Deno にあったから追加したというのも割りと見ます
Deno のほうが先に機能追加されてると言えます
TypeScript 関連でも Deno の対応が早いと感じてます
JavaScript に新規追加された機能で TypeScript 公式に型定義を提供してないので使うと型エラーになるというものがありましたが Deno では独自に対応してたりしました
動かすために Deno のコードから型定義をコピペしてきたこともありました
懸念は std の機能にまだ Unstable が多いところです
以前も全然安定してないし まだいいかと思って詳細は見なかった覚えがあります
でも結構経ってもまだ Unstable ですし 長期的に Unstable のままになってそうです
日付のフォーマットとか HTML のエスケープなどは API や動作が変わりそうなものでもないですし バージョンを固定してれば勝手に動かなくなってるわけでもないので 別にいいかなというところです
const create = (len) => {
return Array.from(Array(len).keys())
}
const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)
console.time()
const result = m.map(id => {
return {
a: a.filter(x => x === id),
b: b.filter(x => x === id),
c: c.filter(x => x === id),
d: d.filter(x => x === id),
}
})
console.timeEnd()
m を基準に各要素に a, b, c, d を id 検索して一致するものを配列で保持する
これだと create ですべて [0, 1, ..., 9999] が入ってるのでフィルターの結果はすべて 1 件
毎回フィルターするのはムダそうに見えるけど
m の 1 要素あたりの処理で 1 万 x 4 = 4 万回の処理
それを m の要素 1 万回なので 4 億回
単に for ループで 4 億回ループしてカウントアップしても 800 ms くらいで 1 秒かからない
また実際には create で作られる a, b, c, d の要素には m の中には含まれない id が多数あって 0 件も多め
事前に a などを id ごとにグループ化できるけど 実際には filter のあとに変換処理の map もあって 使わない要素まで変換するのはムダになりそうという判断で m の map の中で都度フィルター
変換の方がフィルターよりも重たそうと思ってたけど実際は変換は大したことなくて フィルターでかなり遅くなってた
上のコードの実行時間は 21 秒くらい
単純な 4 億回ループの 800ms と比べるとかなり遅い
フィルターを使わずグループ化してみる
上のコードに合わせて フィルターした要素の変換はなし
const create = (len) => {
return Array.from(Array(len).keys())
}
const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)
console.time()
const a_map = new Map()
for (const x of a) {
const arr = a_map.get(x)
if (arr) {
arr.push(x)
} else {
a_map.set(x, [x])
}
}
const b_map = new Map()
for (const x of b) {
const arr = b_map.get(x)
if (arr) {
arr.push(x)
} else {
b_map.set(x, [x])
}
}
const c_map = new Map()
for (const x of c) {
const arr = c_map.get(x)
if (arr) {
arr.push(x)
} else {
c_map.set(x, [x])
}
}
const d_map = new Map()
for (const x of d) {
const arr = d_map.get(x)
if (arr) {
arr.push(x)
} else {
d_map.set(x, [x])
}
}
const result = m.map(id => {
return {
a: a_map.get(id),
b: b_map.get(id),
c: c_map.get(id),
d: d_map.get(id),
}
})
console.timeEnd()
結果は 20ms くらいで 1000 倍くらいの高速化
単純な === が条件のフィルターってほぼ無視できるくらいに考えてたけど結構遅めだった
最近は自分でやらなくても groupBy があるので そっちにしてみるともっと短くかける
const create = (len) => {
return Array.from(Array(len).keys())
}
const m = create(10000)
const a = create(10000)
const b = create(10000)
const c = create(10000)
const d = create(10000)
console.time()
const a_group = Object.groupBy(a, x => x)
const b_group = Object.groupBy(b, x => x)
const c_group = Object.groupBy(c, x => x)
const d_group = Object.groupBy(d, x => x)
const result = m.map(id => {
return {
a: a_group[id],
b: b_group[id],
c: c_group[id],
d: d_group[id],
}
})
console.timeEnd()
プロパティの参照のみならオブジェクトが優れると聞いたのでオブジェクトにしてみたけど 速度はほぼ違いなかった
最近はコードの補完を AI がやってくれるサービスがあると聞きます
ただ有料だったりで気楽に使えなそうと使うことはなかったのですが VisualStudio 2022 で C# を書いていたら補完候補を出してくれました
for 文みたいな毎回同じパターンで書くところは手入力だと面倒なのでかなり楽です
関数名から中身を全部作るみたいのは あまり期待通りに動かなそうで良い印象はなかったですが こういう小さい範囲での補完なら扱いやすそうです
VSCode でも導入したくなりました
でも VSCode の拡張機能でこういうのに対応してるのはどれも有料なんですよね
VisualStudio みたいに標準機能として実装されると嬉しいです
ただ有料だったりで気楽に使えなそうと使うことはなかったのですが VisualStudio 2022 で C# を書いていたら補完候補を出してくれました
for 文みたいな毎回同じパターンで書くところは手入力だと面倒なのでかなり楽です
関数名から中身を全部作るみたいのは あまり期待通りに動かなそうで良い印象はなかったですが こういう小さい範囲での補完なら扱いやすそうです
VSCode でも導入したくなりました
でも VSCode の拡張機能でこういうのに対応してるのはどれも有料なんですよね
VisualStudio みたいに標準機能として実装されると嬉しいです
オブジェクトじゃないしね
zx を使うとコマンドの実行をこう書けます
$ は zx が提供する関数です
テンプレートリテラルのタグ関数として動作します
C# でも似たことをやってるコードを見かけました
どうやってるのだろうと思いましたが C# の場合は $ は文字列中の埋め込みのために付けるもので 見た目は似てますが JavaScript とは違います
文字列の await で実行される処理を追加してるらしいです
それなら JavaScript もできるかもと思って
のようにしてみましたが await では 文字列の then は呼び出されませんでした
thenable オブジェクトと呼ばれるだけあって オブジェクトでないとダメそうです
zx を使うとコマンドの実行をこう書けます
const path = "/tmp"
await $`cd ${path}`
$ は zx が提供する関数です
テンプレートリテラルのタグ関数として動作します
C# でも似たことをやってるコードを見かけました
var path = @"C:\tmp"
await $"cd {path}"
どうやってるのだろうと思いましたが C# の場合は $ は文字列中の埋め込みのために付けるもので 見た目は似てますが JavaScript とは違います
文字列の await で実行される処理を追加してるらしいです
それなら JavaScript もできるかもと思って
String.prototype.then = function() {
// なにか処理
}
await "echo 1"
のようにしてみましたが await では 文字列の then は呼び出されませんでした
thenable オブジェクトと呼ばれるだけあって オブジェクトでないとダメそうです
前の記事で書いたログアウト後に localStorage が残る問題
対処するならどうするのがいいのかなと考えたところ ユーザーの key で暗号化して保存するのが一番かなと思います
暗号化しておけば別ユーザーでログインしてアクセスしたときに復号に失敗するので無視して復元なしになります
単純にログアウト時にクリアということも考えられますが Cookie のセッションや時間切れによる自然ログアウトという可能性もありえます
この場合は localStorage にデータは残ったままです
ログイン時に前回のログインユーザーと違えばクリアすることもできますが ログインする前に直接 localStorage を見ることだってできます
その点では暗号化しておけば安心です
ただ 暗号化は少し重ための処理になるので 入力イベントのたびに localStorage に書き込んだり localStorage からデータを探したりするのは向かなくなります
思いつきでちょっとしたものを作ってみました
https://nexpr.gitlab.io/public-pages/encrypt-storage/example.html
ユーザーを選んでログインボタンを押すとそのユーザーでログインできます
ログインといっても JavaScript で cookieStore に直接書き込む擬似的なものです
ログイン後は textarea が出るのでここに書き込むと内容が自動で localStorage に保存されます
ログアウトして別ユーザーでログインしても復元されません
同じユーザーでログインすると復元されます
localStorage のキーはひとつだけなので 別ユーザーでなにか入力すると上書きされて前のユーザーの入力情報は消えます
ここでは暗号化のキーはユーザー ID を元に適当に作ってますが ちゃんとやる場合は他ユーザーのキーを予想できないようにサーバーでランダムに作って ログイン中のユーザー情報と一緒に受け取るとかが必要です
対処するならどうするのがいいのかなと考えたところ ユーザーの key で暗号化して保存するのが一番かなと思います
暗号化しておけば別ユーザーでログインしてアクセスしたときに復号に失敗するので無視して復元なしになります
単純にログアウト時にクリアということも考えられますが Cookie のセッションや時間切れによる自然ログアウトという可能性もありえます
この場合は localStorage にデータは残ったままです
ログイン時に前回のログインユーザーと違えばクリアすることもできますが ログインする前に直接 localStorage を見ることだってできます
その点では暗号化しておけば安心です
ただ 暗号化は少し重ための処理になるので 入力イベントのたびに localStorage に書き込んだり localStorage からデータを探したりするのは向かなくなります
思いつきでちょっとしたものを作ってみました
https://nexpr.gitlab.io/public-pages/encrypt-storage/example.html
ユーザーを選んでログインボタンを押すとそのユーザーでログインできます
ログインといっても JavaScript で cookieStore に直接書き込む擬似的なものです
ログイン後は textarea が出るのでここに書き込むと内容が自動で localStorage に保存されます
ログアウトして別ユーザーでログインしても復元されません
同じユーザーでログインすると復元されます
localStorage のキーはひとつだけなので 別ユーザーでなにか入力すると上書きされて前のユーザーの入力情報は消えます
ここでは暗号化のキーはユーザー ID を元に適当に作ってますが ちゃんとやる場合は他ユーザーのキーを予想できないようにサーバーでランダムに作って ログイン中のユーザー情報と一緒に受け取るとかが必要です
メインの方のブログと こっちのサブのブログの使い分けですが 基本は長さです
主にセクション分けしたいくらいになってきたらメインの方に書いて そうでもないときはサブの方です
他にはプレビュー機能の有無があります
プレビューボタンを押すとコードブロック内の HTML を実際に別タブで表示するので動きを確認できます
実現方法は新しいタブを開いて window オブジェクト経由で document.open して document.write するというだけ
あんまり好ましい方法じゃないと思いますが こういう用途に限ればありかなと思ってます
ただ 最近のこういう機能を実現してるページって ServiceWorker を使ってます
ServiceWorker を使うと実ページとして URL を持ってアクセスできます
今の方法だと動的に document.write で書き込んでるのでリロードすると再表示できない とか devtools でデバッグするとかで問題があるのですが ServiceWorker にするとそれにも対応できます
サブブログの方は ServiceWorker を使って機能を追加しようかなと考えてます
でも プレビューできるようにするとなると HTML 形式で 1 ページ全体のコードを記載する必要ができます
できるだけ短くシンプルにしたいはずの Short のブログなのに長くなってしまうのは どうなのかなと思ったりもしてます
ほぼ閲覧者はいないと思いますが 一応ライブドアブログはスマホ表示もあって こっちだと共通の処理やスタイルが適用されないので あまり特殊な機能を入れてるとそっちに影響したりします
CustomElements とか実質使えないですし
と考えると Gist などに完全なコードをおいて Githack でページを用意してそこへのリンクをつける程度がいいのかもです
Gist だと Githack を通さないとページとしてアクセスできないので 1 リポジトリにまとめて Pages 機能でもいいかも
(流し見するとタイトルが「コードレビュー」に見えてしまう)
その後
GitLab は VSCode の UI で編集してコミットできるようになってて便利だったので GitLab Pages にしようと思ってます
ただ 気になるところとして Gist を Githack で公開する場合はリビジョンも URL に含まれます
それに対して GitLab Pages は常に最新版です
各バージョンをフォルダとして用意すればできなくもないのですが バージョンで管理するようなちゃんとしたものでもないです
バグ修正レベルなら最新でもいいのですが 大きく変更したりフォルダの移動が起きると当時のバージョンがあったほうがいい気もします
はっきり決まらないですが ブログ記事の古いものなんてそんな見返すこともないので深く考えなくていいかな(諦めた)
主にセクション分けしたいくらいになってきたらメインの方に書いて そうでもないときはサブの方です
他にはプレビュー機能の有無があります
プレビューボタンを押すとコードブロック内の HTML を実際に別タブで表示するので動きを確認できます
実現方法は新しいタブを開いて window オブジェクト経由で document.open して document.write するというだけ
あんまり好ましい方法じゃないと思いますが こういう用途に限ればありかなと思ってます
ただ 最近のこういう機能を実現してるページって ServiceWorker を使ってます
ServiceWorker を使うと実ページとして URL を持ってアクセスできます
今の方法だと動的に document.write で書き込んでるのでリロードすると再表示できない とか devtools でデバッグするとかで問題があるのですが ServiceWorker にするとそれにも対応できます
サブブログの方は ServiceWorker を使って機能を追加しようかなと考えてます
でも プレビューできるようにするとなると HTML 形式で 1 ページ全体のコードを記載する必要ができます
できるだけ短くシンプルにしたいはずの Short のブログなのに長くなってしまうのは どうなのかなと思ったりもしてます
ほぼ閲覧者はいないと思いますが 一応ライブドアブログはスマホ表示もあって こっちだと共通の処理やスタイルが適用されないので あまり特殊な機能を入れてるとそっちに影響したりします
CustomElements とか実質使えないですし
と考えると Gist などに完全なコードをおいて Githack でページを用意してそこへのリンクをつける程度がいいのかもです
Gist だと Githack を通さないとページとしてアクセスできないので 1 リポジトリにまとめて Pages 機能でもいいかも
(流し見するとタイトルが「コードレビュー」に見えてしまう)
その後
GitLab は VSCode の UI で編集してコミットできるようになってて便利だったので GitLab Pages にしようと思ってます
ただ 気になるところとして Gist を Githack で公開する場合はリビジョンも URL に含まれます
それに対して GitLab Pages は常に最新版です
各バージョンをフォルダとして用意すればできなくもないのですが バージョンで管理するようなちゃんとしたものでもないです
バグ修正レベルなら最新でもいいのですが 大きく変更したりフォルダの移動が起きると当時のバージョンがあったほうがいい気もします
はっきり決まらないですが ブログ記事の古いものなんてそんな見返すこともないので深く考えなくていいかな(諦めた)
基本フロントエンドのビルドでファイルをまとめるとき 1 ファイルに全部をバンドルしているのですが 結構重たくなってきました
とりあえずページごとに動的に import するようにして分けたのですが 想像以上にファイル数が増えました
例えば こういう依存関係の場合
A, B, C をそれぞれ動的なインポートにします
メインである index.js 以外に A, B, C の 3 つのファイルができて 4 つかなと思ったのですがそうじゃなかったです
Z は B からも C からも使われます
B と C の両方に Z を入れると両方を使うページでは二重になってムダですし 別モジュールとして扱われてしまう実害も出ます
なので Z は独立したモジュールとして自動的に分割されます
X は A だけで使われ Y は B だけなので これらはそれぞれ A と B に含まれることになります
実際のところ Z みたいに複数箇所で使われるモジュールってかなり多いです
各ページを開いたときにロードするファイルはメインのファイルとそのページ用のファイルの 2 ファイルになるくらいに考えていたのに 実際はそれよりはるかに多かったです
それでも最適化はされてるようで export せず console.log だけみたいな場合は一つのファイルにまとめられたり B と C の両方が Y と Z を読み込む場合は Y と Z は一つのファイルにまとめられるなどしてました
でも実際の大きめのプロジェクトになると まとまらないことも多そうですし ESM で直接ロードするのとそう変わらないくらいファイルをロードすることになりそうです
それならファイルをまとめる必要あるのかなと思いますね
とりあえずページごとに動的に import するようにして分けたのですが 想像以上にファイル数が増えました
例えば こういう依存関係の場合
index.js
A.js
X.js
B.js
Y.js
Z.js
C.js
Z.js
A, B, C をそれぞれ動的なインポートにします
メインである index.js 以外に A, B, C の 3 つのファイルができて 4 つかなと思ったのですがそうじゃなかったです
Z は B からも C からも使われます
B と C の両方に Z を入れると両方を使うページでは二重になってムダですし 別モジュールとして扱われてしまう実害も出ます
なので Z は独立したモジュールとして自動的に分割されます
X は A だけで使われ Y は B だけなので これらはそれぞれ A と B に含まれることになります
実際のところ Z みたいに複数箇所で使われるモジュールってかなり多いです
各ページを開いたときにロードするファイルはメインのファイルとそのページ用のファイルの 2 ファイルになるくらいに考えていたのに 実際はそれよりはるかに多かったです
それでも最適化はされてるようで export せず console.log だけみたいな場合は一つのファイルにまとめられたり B と C の両方が Y と Z を読み込む場合は Y と Z は一つのファイルにまとめられるなどしてました
でも実際の大きめのプロジェクトになると まとまらないことも多そうですし ESM で直接ロードするのとそう変わらないくらいファイルをロードすることになりそうです
それならファイルをまとめる必要あるのかなと思いますね
アニメーションで少し違和感を感じるところがありました
detail と summary タグ的なもので 高さを切り替えて表示の有無を切り替えているものです
UI ライブラリだとアコーディオンとか呼ばれてたりもします
devtools でゆっくりにしてみると 閉じてる途中で中身が消えていました
開くときには途中までアニメーションしてそこからは一気に本来の高さに切り替わっていました
React などのフレームワークならでは感がありますね
単純な開閉ならともかく 選択したものを表示する系だと 閉じてるときは選択中のものがありません
こういう感じで作ってると自然とそうなりそうです
DOM を直接操作するなら選択が解除されても閉じるだけでよく わざわざ中身まで更新するのは面倒なので中身はそのままで放置だったりします
しかし React 的なものだと残すほうが大変です
開閉と状態と選択状態を別に保持して 閉じるのが完了したイベントで選択を解除するとか 選択と画面表示を別にして 選択されてなくても画面表示用のデータは最後の選択を保持するとかしないといけないです
開くときは その時点では開いたときの高さが不明なのでうまくアニメーションできないです
これは useLayoutEffect などでアニメーションさせればできそうな気はしますが ライブラリを使ってる箇所ではうまくいってないようでした
高さ不明なので適当な高さまでアニメーションで開いて 完了後に height を auto に切り替えることで実際の高さになるのですが そこで急にサイズが変わってました
DOM を直接触るとき以外は基本アニメーションはさせないようにして UI ライブラリのデフォルト挙動のみにしてましたが ちゃんとアニメーションさせようとすると大変そうです
detail と summary タグ的なもので 高さを切り替えて表示の有無を切り替えているものです
UI ライブラリだとアコーディオンとか呼ばれてたりもします
devtools でゆっくりにしてみると 閉じてる途中で中身が消えていました
開くときには途中までアニメーションしてそこからは一気に本来の高さに切り替わっていました
React などのフレームワークならでは感がありますね
単純な開閉ならともかく 選択したものを表示する系だと 閉じてるときは選択中のものがありません
こういう感じで作ってると自然とそうなりそうです
<Accordion open={!!data}>
{data && <Internal data={data}/>}
</Accordion>
DOM を直接操作するなら選択が解除されても閉じるだけでよく わざわざ中身まで更新するのは面倒なので中身はそのままで放置だったりします
しかし React 的なものだと残すほうが大変です
開閉と状態と選択状態を別に保持して 閉じるのが完了したイベントで選択を解除するとか 選択と画面表示を別にして 選択されてなくても画面表示用のデータは最後の選択を保持するとかしないといけないです
開くときは その時点では開いたときの高さが不明なのでうまくアニメーションできないです
これは useLayoutEffect などでアニメーションさせればできそうな気はしますが ライブラリを使ってる箇所ではうまくいってないようでした
高さ不明なので適当な高さまでアニメーションで開いて 完了後に height を auto に切り替えることで実際の高さになるのですが そこで急にサイズが変わってました
DOM を直接触るとき以外は基本アニメーションはさせないようにして UI ライブラリのデフォルト挙動のみにしてましたが ちゃんとアニメーションさせようとすると大変そうです
ブログのコードハイライトがところどころ崩れてました
JSX や ${} が入ってくると崩れやすいようです
highlight.js はメジャーアップデートが進んでたのでアップデートしてみました
軽く見た感じでは改善されてるようでした
これに合わせて highlight.js は CDN から動的に取得するよう変更しました
これまでは CDN が落ちてたり重かったりすることも考えて 一応ブログのサーバーにライブラリをアップロードしていました
CDN が落ちることはめったに無いとはいえ 画面表示に関わる部分ですし インポートに失敗すると初期化処理が中断されるのでサイドバーなど関係ない部分もちゃんと表示されなかったり動作しなかったりすることになりますし
ですが そこまで重要なブログでもないですし ライブドアブログのストレージにライブラリを配置すると更新するのが結構面倒だったので CDN に頼ることにしました
highlight.js の言語モジュールについてですが 画面に必要なもののみインポートするようしました
最初は手抜きで 全部のページで同じファイルのロードで考えてました
しかし ごく一部のページでしか使ってないマイナー言語も多くありました
nim とか elm とか vbs とか
こういうのがあるたびにロードする言語を追加するのは面倒です
それに CSS しかハイライトしないページでも 関係ない言語のモジュールをいくつもロードするのはムダです
ということで highlight.js は言語モジュールを含まない core 版をロードして 言語モジュールはすべて別途ロードさせるようすることにしました
ただこうすると 本来は highlight.js 側で管理してくれているエイリアスを自分で管理しないといけなくて面倒でした
JavaScript の言語モジュールは javascript.min.js というファイル名です
js という言語名から自動でこのファイルをインポートできないので 自分でエイリアス情報を管理して js を javascript に置き換えてからモジュールをインポートします
また code タグごとに並列して処理すると 同じ言語の code タグが複数あるときに少し面倒です
言語ごとに Promise を保持するようにして 最初にロードするときに Promise を作って 2 つ目以降では その Promise が解決されたら処理という感じにしないといけません
また 依存関係もあります
HTML だと内部で CSS や JavaScript も表示するという感じです
JavaScript が内部で HTML を表示することもあるので循環参照します
上記のような単純な Promise だと循環参照したときにデッドロック化することもあり 対応がとても面倒です
ここを頑張る気力もなかったので 2 段階の処理にしました
先に今のページに存在する言語とその言語に依存する言語の一覧を作ります
それらを全部インポートしてから ハイライト処理に移ります
全部の言語モジュールをインポートするまでハイライト処理が始まらないので少し遅くなりますが 2 回目以降はキャッシュされてますし 全部の言語と言ってもそのページで使われるものだけで ほとんどのページでは 2, 3言語くらいです
それくらい別にいいかなと楽な方にしました
ところで ライブドアブログのファイルマネージャーは相変わらず使いづらいです
まとめてアップロードやまとめて削除する機能がないです
10 くらいなら手動でやってもいいですが 100 近くもあるとさすがに嫌です
ひとつひとつボタン押してられないので 自動化しました
ブラウザでファイルマネージャーの画面を開いてコンソールで下のコードを実行します
これで今開いてるフォルダ内の全ファイルを上から順番に削除します
最初の img が×アイコンのボタンなので ここのセレクタのところで条件に一致するものだけにしたり 残したいものを除外したりします
いつの頃からかフォルダの削除はできるようになってたので 本当に全部削除だとフォルダを消したほうが早いです
一応 API も用意されてるのですが 使いやすくないし面倒なのでこれのほうが楽だったりします
JSX や ${} が入ってくると崩れやすいようです
highlight.js はメジャーアップデートが進んでたのでアップデートしてみました
軽く見た感じでは改善されてるようでした
これに合わせて highlight.js は CDN から動的に取得するよう変更しました
これまでは CDN が落ちてたり重かったりすることも考えて 一応ブログのサーバーにライブラリをアップロードしていました
CDN が落ちることはめったに無いとはいえ 画面表示に関わる部分ですし インポートに失敗すると初期化処理が中断されるのでサイドバーなど関係ない部分もちゃんと表示されなかったり動作しなかったりすることになりますし
ですが そこまで重要なブログでもないですし ライブドアブログのストレージにライブラリを配置すると更新するのが結構面倒だったので CDN に頼ることにしました
highlight.js の言語モジュールについてですが 画面に必要なもののみインポートするようしました
最初は手抜きで 全部のページで同じファイルのロードで考えてました
しかし ごく一部のページでしか使ってないマイナー言語も多くありました
nim とか elm とか vbs とか
こういうのがあるたびにロードする言語を追加するのは面倒です
それに CSS しかハイライトしないページでも 関係ない言語のモジュールをいくつもロードするのはムダです
ということで highlight.js は言語モジュールを含まない core 版をロードして 言語モジュールはすべて別途ロードさせるようすることにしました
ただこうすると 本来は highlight.js 側で管理してくれているエイリアスを自分で管理しないといけなくて面倒でした
JavaScript の言語モジュールは javascript.min.js というファイル名です
js という言語名から自動でこのファイルをインポートできないので 自分でエイリアス情報を管理して js を javascript に置き換えてからモジュールをインポートします
また code タグごとに並列して処理すると 同じ言語の code タグが複数あるときに少し面倒です
言語ごとに Promise を保持するようにして 最初にロードするときに Promise を作って 2 つ目以降では その Promise が解決されたら処理という感じにしないといけません
また 依存関係もあります
HTML だと内部で CSS や JavaScript も表示するという感じです
JavaScript が内部で HTML を表示することもあるので循環参照します
上記のような単純な Promise だと循環参照したときにデッドロック化することもあり 対応がとても面倒です
ここを頑張る気力もなかったので 2 段階の処理にしました
先に今のページに存在する言語とその言語に依存する言語の一覧を作ります
それらを全部インポートしてから ハイライト処理に移ります
全部の言語モジュールをインポートするまでハイライト処理が始まらないので少し遅くなりますが 2 回目以降はキャッシュされてますし 全部の言語と言ってもそのページで使われるものだけで ほとんどのページでは 2, 3言語くらいです
それくらい別にいいかなと楽な方にしました
ところで ライブドアブログのファイルマネージャーは相変わらず使いづらいです
まとめてアップロードやまとめて削除する機能がないです
10 くらいなら手動でやってもいいですが 100 近くもあるとさすがに嫌です
ひとつひとつボタン押してられないので 自動化しました
ブラウザでファイルマネージャーの画面を開いてコンソールで下のコードを実行します
これで今開いてるフォルダ内の全ファイルを上から順番に削除します
while (true) {
const img = document.querySelector(`.ftpFileList img[alt="削除する"]`)
if (!img) break
img.click()
await new Promise(r => setTimeout(r, 1000))
const ok = document.querySelector(`input[value="削除"]`)
if (!ok) {
alert("削除ボタンがみつからない")
break
}
ok.click()
await new Promise(r => setTimeout(r, 1000))
}
最初の img が×アイコンのボタンなので ここのセレクタのところで条件に一致するものだけにしたり 残したいものを除外したりします
いつの頃からかフォルダの削除はできるようになってたので 本当に全部削除だとフォルダを消したほうが早いです
一応 API も用意されてるのですが 使いやすくないし面倒なのでこれのほうが楽だったりします
前記事から続きます
HTMLCollection という特性上 遅そうに思ってましたが速かったです
プロパティにアクセスするたびに 検索していたらこの速度にはならないと思うので 工夫はされてるはずです
思いつくのだと DOM の更新時に変更が内部的に通知され その変更が HTMLCollection の監視対象であれば内部状態を更新している とかでしょうか?
それだと有効な HTMLCollection があれば DOM の更新速度が遅くなりそうです
試しに数百の HTMLCollection を作成してから body の子孫要素を大きく書き換えてみました
そのときの DOM 更新にかかった時間と HTMLCollection を作らない場合の DOM 更新にかかった時間を比べてみました
結果は差があるとは言えないくらいのものでした
どのタイミングで処理されてるのでしょうね
HTMLCollection という特性上 遅そうに思ってましたが速かったです
プロパティにアクセスするたびに 検索していたらこの速度にはならないと思うので 工夫はされてるはずです
思いつくのだと DOM の更新時に変更が内部的に通知され その変更が HTMLCollection の監視対象であれば内部状態を更新している とかでしょうか?
それだと有効な HTMLCollection があれば DOM の更新速度が遅くなりそうです
試しに数百の HTMLCollection を作成してから body の子孫要素を大きく書き換えてみました
そのときの DOM 更新にかかった時間と HTMLCollection を作らない場合の DOM 更新にかかった時間を比べてみました
結果は差があるとは言えないくらいのものでした
どのタイミングで処理されてるのでしょうね
TypeScript のコードを見ていたときのこと
みたいなコードがありました
TypeScript の ! って JavaScript の ?. の ? みたいなポジションだと思ってたのですが違ったようです
!. でまとまってる必要はなくて ! だけの単体で使えて 直前の式の nullable を解除するようです
任意の式の末尾に ! が書けるようです
それって JavaScript の構文と競合するようなしないような
JavaScript での ! は式の前に書く単項演算子です
! の次は式である必要があります
一方 TypeScript の ! は式の末尾に書きます
競合するには 「expression expression」 が有効でないとだめです
「(() => {}) ``」 はどちらも式で満たしてますが特殊なもので後者はテンプレートリテラルである必要があります
! が入ることができないので競合できないです
意外と大丈夫なのでしょうか
1 つ特殊なケースで競合を見つけることができましたが 構文的に発生する場所でどっちかが明確なので問題にはならないものでした
そのケースというのはこれです
ひとつめの解釈方法はこうです
このときの yield はキーワードで function*() {} の中でのみ書けます
yield false と同じです
もうひとつはこれです
function*() {} の外なら yield を変数に使えるので 変数に ! をつけたものと 1 との足し算です
function*() {} の中か外かで完全に分かれるので競合にはならないです
あと strict mode なら yield は変数に使えないです
fn(value!)
みたいなコードがありました
TypeScript の ! って JavaScript の ?. の ? みたいなポジションだと思ってたのですが違ったようです
!. でまとまってる必要はなくて ! だけの単体で使えて 直前の式の nullable を解除するようです
任意の式の末尾に ! が書けるようです
それって JavaScript の構文と競合するようなしないような
JavaScript での ! は式の前に書く単項演算子です
! の次は式である必要があります
一方 TypeScript の ! は式の末尾に書きます
競合するには 「expression expression」 が有効でないとだめです
「(() => {}) ``」 はどちらも式で満たしてますが特殊なもので後者はテンプレートリテラルである必要があります
! が入ることができないので競合できないです
意外と大丈夫なのでしょうか
1 つ特殊なケースで競合を見つけることができましたが 構文的に発生する場所でどっちかが明確なので問題にはならないものでした
そのケースというのはこれです
yield!+1
ひとつめの解釈方法はこうです
function* gen() {
yield !+1
}
このときの yield はキーワードで function*() {} の中でのみ書けます
yield false と同じです
もうひとつはこれです
const yield = 1
yield! + 1
function*() {} の外なら yield を変数に使えるので 変数に ! をつけたものと 1 との足し算です
function*() {} の中か外かで完全に分かれるので競合にはならないです
あと strict mode なら yield は変数に使えないです
input や textarea のフォントってどうするのがいいんでしょうか
デフォルトを見てみると input と textarea では異なるようです
またブラウザでも異なります
Windows 環境で Chrome/Edge (117) を見ると
input:
ユーザーエージェントの font-family: 空
表示されるフォント: Arial, Meiryo
textarea:
ユーザーエージェントの font-family: monospace
表示されるフォント: MS Gothic
Windows 環境で Firefox (118) を見ると
input:
ユーザーエージェントの font-family: MS Shell Dlg 2
表示されるフォント: tahoma, Meiryo
textarea:
ユーザーエージェントの font-family: monospace
表示されるフォント: consolas, MS Gothic
どちらも等幅かと思ったら input はそうではないようでした
また等幅フォントのデフォルトはギザギザして汚い MS Gothic でした
monospace で表示されるフォントはブラウザの設定で変えることができ 私は別フォントにしてたので全然気づいてませんでしたが 設定してないブラウザでみるとすごく残念な画面になってました
(Chrome のフォント設定画面は chrome://settings/fonts)
基本的にはデフォルトのものにしておきたいですが さすがにこれは汚すぎで見るに堪えないです
変えておこうかなと思っても標準フォントで等幅ってこれといったのが思いつかないです
UI ライブラリの入力コンポーネントや世の中のページを見てると そもそも等幅にしてるところがそんなに多くないようでした
等幅にしているところはだいたいウェブフォントで独自のフォントを使っているようです
可変幅のところでは inherit にして親と一緒 つまり div や p などの通常のテキストと同じフォントにしてるのを見かけました
また input と textarea でフォントを分けず まとめて同じ可変幅のフォントを設定していたりです
たしかに分ける必要って特に無いと思いますし 等幅じゃなくて困ったということもありません
とりあえず本文と同じ Meiryo でいいかなと思いました
この考えでいくと テキストエディタも可変幅でも良さそうに思えてきました
プログラムで 「=」 や 「:」 や 「//」 の位置など 行頭以外の場所を縦に揃えたがる人は抵抗がありそうですが 行頭以外は基本揃える必要ないって私からすると別に問題無い気もします
考えてみると 可変幅になってる textarea でコードを書くことって普通にあります
「見直してみたら可変幅だね ここ」 くらいに気にしてなかったです
また 日本語がない半角フォントが設定されていて 半角のみ等幅になっていて 日本語が入ると可変幅になるところもありました
フォントの違いが気になりはしますが 一時的なところなら実害はないので放置でした
VSCode のフォントを Meiryo にしてもいいかななんて思いましたが 考えてみたらプログラミング用フォントは等幅なだけではなく 紛らわしい文字の差別化もしてくれてるのですよね
やっぱりプログラミング用フォントを使うのが無難そうです
他には 等幅のほうがパフォーマンス的にも優れてそうな気はしました
文字関係なく幅が固定のほうが事前にサイズ計算ができて重たいファイルを開いて画面に表示するときは差が出そうです
と思いましたが等幅でも半角全角が存在しますし 絵文字みたいなところでは完全に等幅になっていないです
それらも考慮しないといけないので あまり変わらないかもです
あとは 矩形選択したいようなものを扱うときでしょうか
ただこれはマルチカーソル機能があればそっちのほうが高度なので 重要でないかもしれません
どちらかというと SQL の結果など 表形式のテキストを貼り付けてきれいに見えないほうが困りそうです
デフォルトを見てみると input と textarea では異なるようです
またブラウザでも異なります
Windows 環境で Chrome/Edge (117) を見ると
input:
ユーザーエージェントの font-family: 空
表示されるフォント: Arial, Meiryo
textarea:
ユーザーエージェントの font-family: monospace
表示されるフォント: MS Gothic
Windows 環境で Firefox (118) を見ると
input:
ユーザーエージェントの font-family: MS Shell Dlg 2
表示されるフォント: tahoma, Meiryo
textarea:
ユーザーエージェントの font-family: monospace
表示されるフォント: consolas, MS Gothic
どちらも等幅かと思ったら input はそうではないようでした
また等幅フォントのデフォルトはギザギザして汚い MS Gothic でした
monospace で表示されるフォントはブラウザの設定で変えることができ 私は別フォントにしてたので全然気づいてませんでしたが 設定してないブラウザでみるとすごく残念な画面になってました
(Chrome のフォント設定画面は chrome://settings/fonts)
基本的にはデフォルトのものにしておきたいですが さすがにこれは汚すぎで見るに堪えないです
変えておこうかなと思っても標準フォントで等幅ってこれといったのが思いつかないです
UI ライブラリの入力コンポーネントや世の中のページを見てると そもそも等幅にしてるところがそんなに多くないようでした
等幅にしているところはだいたいウェブフォントで独自のフォントを使っているようです
可変幅のところでは inherit にして親と一緒 つまり div や p などの通常のテキストと同じフォントにしてるのを見かけました
また input と textarea でフォントを分けず まとめて同じ可変幅のフォントを設定していたりです
たしかに分ける必要って特に無いと思いますし 等幅じゃなくて困ったということもありません
とりあえず本文と同じ Meiryo でいいかなと思いました
この考えでいくと テキストエディタも可変幅でも良さそうに思えてきました
プログラムで 「=」 や 「:」 や 「//」 の位置など 行頭以外の場所を縦に揃えたがる人は抵抗がありそうですが 行頭以外は基本揃える必要ないって私からすると別に問題無い気もします
考えてみると 可変幅になってる textarea でコードを書くことって普通にあります
「見直してみたら可変幅だね ここ」 くらいに気にしてなかったです
また 日本語がない半角フォントが設定されていて 半角のみ等幅になっていて 日本語が入ると可変幅になるところもありました
フォントの違いが気になりはしますが 一時的なところなら実害はないので放置でした
VSCode のフォントを Meiryo にしてもいいかななんて思いましたが 考えてみたらプログラミング用フォントは等幅なだけではなく 紛らわしい文字の差別化もしてくれてるのですよね
やっぱりプログラミング用フォントを使うのが無難そうです
他には 等幅のほうがパフォーマンス的にも優れてそうな気はしました
文字関係なく幅が固定のほうが事前にサイズ計算ができて重たいファイルを開いて画面に表示するときは差が出そうです
と思いましたが等幅でも半角全角が存在しますし 絵文字みたいなところでは完全に等幅になっていないです
それらも考慮しないといけないので あまり変わらないかもです
あとは 矩形選択したいようなものを扱うときでしょうか
ただこれはマルチカーソル機能があればそっちのほうが高度なので 重要でないかもしれません
どちらかというと SQL の結果など 表形式のテキストを貼り付けてきれいに見えないほうが困りそうです
最近はメソッドチェインを長く書くようなライブラリをあまり使うことがなかったのですが 久々につかうとやっぱりチェインに条件をつけたくなります
こういうの
ライブラリによっては考慮されていて チェイン内で分岐できるようなメソッドがあったりします
しかし分岐したいなら変数にいれて if を使ってというスタンスのライブラリもあります
そうするとメソッドチェインできれいに書けるメリットが失われます
JavaScript の機能が増えてきているといってもやっぱりこういうのはいい方法がないです
ありがちなのがラップして独自の分岐メソッドを追加する方法
しかし method1 などはラップしたオブジェクトが持っていないので Proxy が必要になります
また最後にアンラップして中身を取り出さないといけないです
このタイプは過去何度か作ってみても 利便性のいまいちさや Proxy に抵抗があって実際には使ってないのですよね
全てのオブジェクトにメソッドを追加する prototype 拡張がもっとシンプルになるのですが prototype 拡張は積極的に使うのはどうかなというところです
特に全てに影響する Object.prototype ですし
ただ キーがシンボルなら 影響はほぼなさそうですし ありなのかなと思ったり
チェインの記法がちょっと変わるのが専用構文ぽくて ひとつ上の例よりは好きかもしれないです
動かす用のサンプル
こういうの
const val = new A()
.method1()
.method2() // ← flag が true のときだけ追加したい
.method3()
ライブラリによっては考慮されていて チェイン内で分岐できるようなメソッドがあったりします
しかし分岐したいなら変数にいれて if を使ってというスタンスのライブラリもあります
そうするとメソッドチェインできれいに書けるメリットが失われます
JavaScript の機能が増えてきているといってもやっぱりこういうのはいい方法がないです
ありがちなのがラップして独自の分岐メソッドを追加する方法
const val = $(new A())
.method1()
.$(flag, $ => $.method2())
.method3()
.unwrap()
しかし method1 などはラップしたオブジェクトが持っていないので Proxy が必要になります
また最後にアンラップして中身を取り出さないといけないです
このタイプは過去何度か作ってみても 利便性のいまいちさや Proxy に抵抗があって実際には使ってないのですよね
全てのオブジェクトにメソッドを追加する prototype 拡張がもっとシンプルになるのですが prototype 拡張は積極的に使うのはどうかなというところです
特に全てに影響する Object.prototype ですし
ただ キーがシンボルなら 影響はほぼなさそうですし ありなのかなと思ったり
const val = new A()
.method1()
[chain_if](flag,
$ => $.method2(),
)
.method3()
チェインの記法がちょっと変わるのが専用構文ぽくて ひとつ上の例よりは好きかもしれないです
動かす用のサンプル
Object.prototype[Symbol.for("if")] = function(cond, fn) {
if (cond) {
return fn(this)
} else {
return this
}
}
class A {
arr = []
method1() {
this.arr.push(1)
return this
}
method2() {
this.arr.push(2)
return this
}
method3() {
this.arr.push(3)
return this
}
}
const flag = true // or false
const val = new A()
.method1()
[Symbol.for("if")](flag,
$ => $.method2(),
)
.method3()
console.log(val)
ちょっと前に見かけた JavaScript のセミコロンの話題
初心者だとどこに書けばいいのかわからない という話でした
たしかになくても動いてしまう以上 初心者はどこで必要なのか分かりづらい気がしますね
C や PHP など類似の構文でセミコロン必須の言語に触れたことがあれば そこではセミコロンが無いとエラーなのでちゃんとルールを把握してなくても使っていれば感覚で必須な場所はわかってきます
その流れで JavaScript を書いていれば セミコロンを文末に書く場合に必要な場所には困らないと思います
しかし JavaScript が初めてなら 要らないところにつけても 必要な場所につけなくても動きます
ちゃんと構文を理解してなければ難しそうです
基本は文末に書く と言っても if や for 文や関数宣言では不要です
セミコロンを書かない部分は 改行もセミコロンもなく 1 行にまとめて動く部分です
上は動きますが 下は構文エラーで動かないです
なぜかというと 上は関数宣言なので 「}」 が来るとそこで文の終わりと判断できます
なので自然と次のトークンの a は新しい文の始まりとわかります
下だと代入式なので 「}」 で文が終わるかは不明です
なので次のトークンの a も読み取ってみて ここで構文エラーになります
それならここで自動で文を分割してくれれば と思わなくもないですが 先を読んでみないとわからないのはパフォーマンスやパーサーの複雑度に影響しそうですし 構文エラーのバグが意図せず動いてしまうとかあるのでしょう
話を戻して 初心者にこういうところまで理解してというのは無理があると思います
構文チェックのツールやフォーマッターを使えば良いという意見もありそうです
ですが これも初心者がいきなり使うにはハードルがあるように思います
初心者というのは環境を準備というのが一番苦労するところです
JavaScript はメモ帳レベルのテキストエディタでも HTML を書いて script タグに JavaScript コードを書けばとりあえず動くという手軽さが魅力なのに こういうのの準備ってそういうメリットが失われますからね
なので個人的にはセミコロンは書かないほうがいいと思ってます
コード上入れるとしてもフォーマッターが入れて 人が書く必要はないと思います
ただその議論では予想外の方法を取ってる人がいました
すべての文にはセミコロンを入れるルールにしているようです
if や for のあとに入れても意味ないだけで別に問題ないですからね
たしかに統一感があり 迷うこともないので それならそれでいいんじゃないかなと思えました
こんな感じでしょうか
ただ最後に } が並ぶとき セミコロンなしのほうが全部が揃って見た目がきれいには思います
意味が違う } でも全部一緒だとむしろ分かりづらいという考え方もできますが
初心者だとどこに書けばいいのかわからない という話でした
たしかになくても動いてしまう以上 初心者はどこで必要なのか分かりづらい気がしますね
C や PHP など類似の構文でセミコロン必須の言語に触れたことがあれば そこではセミコロンが無いとエラーなのでちゃんとルールを把握してなくても使っていれば感覚で必須な場所はわかってきます
その流れで JavaScript を書いていれば セミコロンを文末に書く場合に必要な場所には困らないと思います
しかし JavaScript が初めてなら 要らないところにつけても 必要な場所につけなくても動きます
ちゃんと構文を理解してなければ難しそうです
基本は文末に書く と言っても if や for 文や関数宣言では不要です
セミコロンを書かない部分は 改行もセミコロンもなく 1 行にまとめて動く部分です
function a() {} a()
const a = function() {} a()
上は動きますが 下は構文エラーで動かないです
なぜかというと 上は関数宣言なので 「}」 が来るとそこで文の終わりと判断できます
なので自然と次のトークンの a は新しい文の始まりとわかります
下だと代入式なので 「}」 で文が終わるかは不明です
なので次のトークンの a も読み取ってみて ここで構文エラーになります
それならここで自動で文を分割してくれれば と思わなくもないですが 先を読んでみないとわからないのはパフォーマンスやパーサーの複雑度に影響しそうですし 構文エラーのバグが意図せず動いてしまうとかあるのでしょう
話を戻して 初心者にこういうところまで理解してというのは無理があると思います
構文チェックのツールやフォーマッターを使えば良いという意見もありそうです
ですが これも初心者がいきなり使うにはハードルがあるように思います
初心者というのは環境を準備というのが一番苦労するところです
JavaScript はメモ帳レベルのテキストエディタでも HTML を書いて script タグに JavaScript コードを書けばとりあえず動くという手軽さが魅力なのに こういうのの準備ってそういうメリットが失われますからね
なので個人的にはセミコロンは書かないほうがいいと思ってます
コード上入れるとしてもフォーマッターが入れて 人が書く必要はないと思います
ただその議論では予想外の方法を取ってる人がいました
すべての文にはセミコロンを入れるルールにしているようです
if や for のあとに入れても意味ないだけで別に問題ないですからね
たしかに統一感があり 迷うこともないので それならそれでいいんじゃないかなと思えました
こんな感じでしょうか
function a(value) {
if (value > 1) {
for (let i = 0; i< value; i++) {
console.log(foo(i));
};
} else {
console.log(value);
};
return {
foo: {
bar: {
baz: () => {
}
}
}
};
};
ただ最後に } が並ぶとき セミコロンなしのほうが全部が揃って見た目がきれいには思います
}
}
}
}
}
意味が違う } でも全部一緒だとむしろ分かりづらいという考え方もできますが
React などのライブラリを使わず DOM 操作で作ってるページだと なにかが変わったときにリアルタイムにあっちもこっちも反映って面倒です
だから確定ボタンを押したみたいなときに全体に反映させて それまでは外部に影響させないとか最小限の部分だけに反映させたりということが多いです
そうなってるものを動きを変えずに React で置き換えそうとすると 逆に面倒なんですよね
React だと state に持ってる情報から今の画面状態を作るので普通にやると全部が変わってしまいます
確定ボタンを押すまでは他に影響しないようにしたいなら 新たに state を用意する必要があります
グローバルの state と ローカルの state を用意して 普段はローカルの方だけを更新して ボタンが押されたらローカルをグローバルに反映するみたいなことになります
(グローバルと言ってもその state を使う範囲でのグローバルなのでページ内だったりタブ内だったりで プログラム的なグローバル変数というわけではないです)
React だと state 特にコンポーネントローカルの state はあまり増やしたくないのですよね
ローカルに state を持つことでそれを更新するための処理も必要になります
特に props や別フックの更新に応じて state を更新する必要があれば 変更を監視して更新するための useEffect もセットで必要になります
フォームや state 系のライブラリを見てみても 他の値の更新で state を更新する必要がある以上 どれもこの問題あるように思うのですが いい感じに解決できるものがあるのでしょうか
だから確定ボタンを押したみたいなときに全体に反映させて それまでは外部に影響させないとか最小限の部分だけに反映させたりということが多いです
そうなってるものを動きを変えずに React で置き換えそうとすると 逆に面倒なんですよね
React だと state に持ってる情報から今の画面状態を作るので普通にやると全部が変わってしまいます
確定ボタンを押すまでは他に影響しないようにしたいなら 新たに state を用意する必要があります
グローバルの state と ローカルの state を用意して 普段はローカルの方だけを更新して ボタンが押されたらローカルをグローバルに反映するみたいなことになります
(グローバルと言ってもその state を使う範囲でのグローバルなのでページ内だったりタブ内だったりで プログラム的なグローバル変数というわけではないです)
React だと state 特にコンポーネントローカルの state はあまり増やしたくないのですよね
ローカルに state を持つことでそれを更新するための処理も必要になります
特に props や別フックの更新に応じて state を更新する必要があれば 変更を監視して更新するための useEffect もセットで必要になります
フォームや state 系のライブラリを見てみても 他の値の更新で state を更新する必要がある以上 どれもこの問題あるように思うのですが いい感じに解決できるものがあるのでしょうか
未だに React 18 より 16 や 17 を使ってることが多いです
そんななので今更気づいたのですが 18 では コンポーネントのアンマウント後に state を更新したときに出ていた警告がなくなっていました
こういうの
表示させる例
toggle ボタンのクリックで Component の有無を切り替えます
Component はボタンをクリックするとカウントアップするコンポーネントですが 3 秒ディレイしてからカウントアップします
この 3 秒の間に Component を表示しないようにすると アンマウントされて その後に state の更新が起きるので警告が出ます
実際のケースでは fetch など非同期処理を行うコンポーネントで その処理が完了する前に別のページに遷移したりタブを切り替えたりすることで発生することが多いです
これの対処をちゃんとやると面倒です
アンマウント済みかを示すフラグを用意してアンマウント時に更新します
state 更新時にはフラグを見てアンマウント済みなら更新をスキップします
面倒な上にコードも複雑化する嫌なやつです
実際こういう一回限りの setState がアンマウント後に行われてもメモリリークにはならないです
それでも警告として出る以上対処しようとして その結果 面倒が多く複雑になるという問題があったのでなくしたのかもですね
メモリリークになるのは setInterval や外部のサブスクリプションなどで 1 回限りではなくイベントが起きるたびに更新が発生するものです
こういうのです
この警告は出ていても無視していいケースが多かったですが 無くしてしまうとこういうケースに気づけないという問題はありそうです
そんななので今更気づいたのですが 18 では コンポーネントのアンマウント後に state を更新したときに出ていた警告がなくなっていました
こういうの
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
表示させる例
export default () => {
const [shown, setShown] = useState(false)
return (
<div>
<button onClick={() => setShown(!shown)}>toggle</button>
{shown && <Component/>}
</div>
)
}
const Component = () => {
const [count, setCount] = useState(0)
const onClick = () => {
setTimeout(() => {
setCount(count => count + 1)
}, 3000)
}
return (
<div>
<button onClick={onClick}>{count}</button>
</div>
)
}
toggle ボタンのクリックで Component の有無を切り替えます
Component はボタンをクリックするとカウントアップするコンポーネントですが 3 秒ディレイしてからカウントアップします
この 3 秒の間に Component を表示しないようにすると アンマウントされて その後に state の更新が起きるので警告が出ます
実際のケースでは fetch など非同期処理を行うコンポーネントで その処理が完了する前に別のページに遷移したりタブを切り替えたりすることで発生することが多いです
これの対処をちゃんとやると面倒です
アンマウント済みかを示すフラグを用意してアンマウント時に更新します
state 更新時にはフラグを見てアンマウント済みなら更新をスキップします
面倒な上にコードも複雑化する嫌なやつです
実際こういう一回限りの setState がアンマウント後に行われてもメモリリークにはならないです
それでも警告として出る以上対処しようとして その結果 面倒が多く複雑になるという問題があったのでなくしたのかもですね
メモリリークになるのは setInterval や外部のサブスクリプションなどで 1 回限りではなくイベントが起きるたびに更新が発生するものです
こういうのです
useEffect(() => {
setInterval(() => {
setState(Date.now())
}, 1000)
}, [])
この警告は出ていても無視していいケースが多かったですが 無くしてしまうとこういうケースに気づけないという問題はありそうです
ステータスや結果として受け取る情報で成功や失敗が入ってるときなんて英単語にするか
名詞の 成功・失敗 だと success / failure
動詞の 成功する・失敗する だと succeed / fail
過去分詞形の 成功した・失敗した だと succeeded / failed
success / failure か succeeded / failed の組み合わせのどっちかかなと思うけど succeeded ってほとんど目にしない
ググっても Windows 系のドキュメントとかが出てくるくらい
じゃあ success / failure かというと failure もけっこうレアな気がする
success と failed もしくは success / error をよく見るような気がする
よく考えると対称じゃない気がするけど 英語が怪しい日本人が作ったものじゃなく 海外のサービスでも見かけるものだし これでいいのかな
名詞の 成功・失敗 だと success / failure
動詞の 成功する・失敗する だと succeed / fail
過去分詞形の 成功した・失敗した だと succeeded / failed
success / failure か succeeded / failed の組み合わせのどっちかかなと思うけど succeeded ってほとんど目にしない
ググっても Windows 系のドキュメントとかが出てくるくらい
じゃあ success / failure かというと failure もけっこうレアな気がする
success と failed もしくは success / error をよく見るような気がする
よく考えると対称じゃない気がするけど 英語が怪しい日本人が作ったものじゃなく 海外のサービスでも見かけるものだし これでいいのかな
OSS の package.json を見てると wireit というキーがあるのを見つけました
なにかと思って調べたら Google 製のツールで npm run を拡張してくれるものみたいです
https://github.com/google/wireit
便利そうなものかなと期待して詳しい機能を見ていたのですが 思ってたのと違ってそうでした
コマンド間に依存関係を定義できて 依存するコマンドも自動で実行してくれたり 並列に実行したりとかそういうのがメインみたいです
またインクリメンタルビルドやキャッシュやウォッチ機能があり そのために各コマンドの入力と出力のファイルを書かないといけないようです
たしかにこういった機能の実現だと必要そうですが これが結構面倒そうです
それに各コマンド側でもすでに持ってる情報なので二重に管理することになり 漏れが出そうですし
また環境変数を設定できるようですが静的な値のみのようです
求めてるのはコマンド実行時に OS 依存せず指定できることなので求めてることはできなさそうです
こういうのがやりたいのです
期待してたのとは違ったので私は使わなそうです
Github アクションとの統合もあるようですし OSS 向けなんでしょうかね
なにかと思って調べたら Google 製のツールで npm run を拡張してくれるものみたいです
https://github.com/google/wireit
便利そうなものかなと期待して詳しい機能を見ていたのですが 思ってたのと違ってそうでした
コマンド間に依存関係を定義できて 依存するコマンドも自動で実行してくれたり 並列に実行したりとかそういうのがメインみたいです
またインクリメンタルビルドやキャッシュやウォッチ機能があり そのために各コマンドの入力と出力のファイルを書かないといけないようです
たしかにこういった機能の実現だと必要そうですが これが結構面倒そうです
それに各コマンド側でもすでに持ってる情報なので二重に管理することになり 漏れが出そうですし
また環境変数を設定できるようですが静的な値のみのようです
求めてるのはコマンド実行時に OS 依存せず指定できることなので求めてることはできなさそうです
こういうのがやりたいのです
期待してたのとは違ったので私は使わなそうです
Github アクションとの統合もあるようですし OSS 向けなんでしょうかね
特に使ってこなかったけど ライブドアブログの機能で通常のリンクとは別にリッチリンクって機能があります
一部のブログでは見かけるもので 大きめのブロックで記事タイトルや画像が表示されたりします
こんなの↓
テキストや画像をリンクにせず 単純に https://~~ みたいな URL をリンクにしてるのならこれのほうがよかったりするのかもとふと思いました
ですが中身を見てみると いまいちなものでした
iframe になっていて src には専用ドメインで UUID くらいしか情報がありません
https://richlink.blogsys.jp/embed/7a07f8d3-1c61-333f-b67f-31d0933ba2cd
です
UUID だけで元 URL の情報がないので扱いづらいです
元 URL を知るために上のページにアクセスしないといけません
それにもし将来的にこのリッチリンク機能が終了したら 復元手段がないです
変なことせず素直にパス部分に元 URL をそのまま入れる形式にしておいてくれればよかったのに
一部のブログでは見かけるもので 大きめのブロックで記事タイトルや画像が表示されたりします
こんなの↓
テキストや画像をリンクにせず 単純に https://~~ みたいな URL をリンクにしてるのならこれのほうがよかったりするのかもとふと思いました
ですが中身を見てみると いまいちなものでした
iframe になっていて src には専用ドメインで UUID くらいしか情報がありません
<iframe frameborder="0" scrolling="no" style="height: 120px; width: 580px; max-width: 100%; vertical-align:top;" src="https://richlink.blogsys.jp/embed/7a07f8d3-1c61-333f-b67f-31d0933ba2cd"></iframe>
https://richlink.blogsys.jp/embed/7a07f8d3-1c61-333f-b67f-31d0933ba2cd
です
UUID だけで元 URL の情報がないので扱いづらいです
元 URL を知るために上のページにアクセスしないといけません
それにもし将来的にこのリッチリンク機能が終了したら 復元手段がないです
変なことせず素直にパス部分に元 URL をそのまま入れる形式にしておいてくれればよかったのに
たいていのフォーマッターは 1 行の文字数を設定できます
指定値を超えると適当なところで自動で改行されます
80 文字だと少なすぎて インデントが深くなるところだとすぐ折り返されて逆に見づらくなります
なので 120 文字くらいにしてるのですが インデントが深くならないところだと長すぎてこれも見づらいです
トップレベルや 1 段階のインデントで 120 文字だと結構ありますからね
それくらいの長さのメソッドチェーンや配列だと折り返したくなります
元コードの折り返しを優先してくれるフォーマッターなら問題ないのですが それを無視して指定文字数に収まるなら改行を消すようなフォーマッターもあります
そう考えると 行幅の数え方を左端からじゃなくてインデントを除外してから数えるのがちょうどいい気がしますね
インデント除外で 80 文字くらいだとインデントの深さを気にせずいつでもちょうどいい長さになりそうです
指定値を超えると適当なところで自動で改行されます
80 文字だと少なすぎて インデントが深くなるところだとすぐ折り返されて逆に見づらくなります
なので 120 文字くらいにしてるのですが インデントが深くならないところだと長すぎてこれも見づらいです
トップレベルや 1 段階のインデントで 120 文字だと結構ありますからね
それくらいの長さのメソッドチェーンや配列だと折り返したくなります
元コードの折り返しを優先してくれるフォーマッターなら問題ないのですが それを無視して指定文字数に収まるなら改行を消すようなフォーマッターもあります
そう考えると 行幅の数え方を左端からじゃなくてインデントを除外してから数えるのがちょうどいい気がしますね
インデント除外で 80 文字くらいだとインデントの深さを気にせずいつでもちょうどいい長さになりそうです
const {prop1, prop2} = obj
const [item1, item2] = arr
というコードを dprint や Prettier などでフォーマットするとデフォルトだと
const { prop1, prop2 } = obj
const [item1, item2] = arr
{} だけ内側にスペースが入ってる非対称感が気になる
オブジェクトは
const { foo: bar } = { foo: 1 }
のように 1 要素に key と value みたいな複数を書くから前後の空白がある方が見やすいとか?
でも配列だって
const [a = 1, b = 2] = [10]
って書いたら同じようなものだと思うけど
ちなみに console.log での出力の場合 ブラウザ (devtools) 上ではオブジェクトも配列も前後にスペースなし
Node.js だとどっちもスペースあり
> console.log({a:1})
{ a: 1 }
> console.log([1,2])
[ 1, 2 ]
今では at メソッドがあるので
という風に簡単に取得できます
これが無い頃は不便でした
と items を 2 回参照するので一旦変数に入れる必要があります
ひとつの式の中で書きたいときに扱いづらいです
なので関数を作るのですが 関数だと配列を取得する式全体を囲む必要があるので書きづらいです
なので Array.prototype を拡張して last みたいなメソッドを追加していました
しかしこれでも 最初の取得の [0] とは非対称感があっていまいちです
わかりやすさ的には first メソッドも作ればいいのかもですが コードが長くなります
特に Python も書いていた頃だったので
のスッキリとした書き方に憧れます
実際に使うのは「最後」のみで 後ろから N 番目なんてまずないので -1 だけ対応すればいいかとこんなことをしてみたりしました
-2 もいけそうなのに動かないし 色々問題はあって実用はためらわれるものの結構好きな方法です
ためらった結果使わず prototype を拡張する last もなんかなぁ ということで結局はこんな形でした
でもこれをみては [-1] って書けるようにしたいなぁと
at が使えるようになっても 最初は [0] なのに最後は .at(-1) になる非対称感のイマイチさを感じてますし かといって 最初や 2 番目の取得でわざわざ at を使おうとは思いません
最後を取得したいってことがあると [-1] で取得できるようにしたいなーと思っては諦めてる状況です
[1, 2].at(-1) // 2
という風に簡単に取得できます
これが無い頃は不便でした
items[items.length - 1]
と items を 2 回参照するので一旦変数に入れる必要があります
ひとつの式の中で書きたいときに扱いづらいです
なので関数を作るのですが 関数だと配列を取得する式全体を囲む必要があるので書きづらいです
なので Array.prototype を拡張して last みたいなメソッドを追加していました
しかしこれでも 最初の取得の [0] とは非対称感があっていまいちです
わかりやすさ的には first メソッドも作ればいいのかもですが コードが長くなります
特に Python も書いていた頃だったので
[1, 2][0] # 1
[1, 2][-1] # 2
のスッキリとした書き方に憧れます
実際に使うのは「最後」のみで 後ろから N 番目なんてまずないので -1 だけ対応すればいいかとこんなことをしてみたりしました
Object.defineProperty(Array.prototype, "-1", { get: function () { return this[this.length - 1] } })
;[1, 2][-1] // 2
-2 もいけそうなのに動かないし 色々問題はあって実用はためらわれるものの結構好きな方法です
ためらった結果使わず prototype を拡張する last もなんかなぁ ということで結局はこんな形でした
[1, 2].slice(-1)[0]
でもこれをみては [-1] って書けるようにしたいなぁと
at が使えるようになっても 最初は [0] なのに最後は .at(-1) になる非対称感のイマイチさを感じてますし かといって 最初や 2 番目の取得でわざわざ at を使おうとは思いません
最後を取得したいってことがあると [-1] で取得できるようにしたいなーと思っては諦めてる状況です
便利そうかもと思ったらやっぱり違うなと思ったりで ちゃんと使うことはなかったライブラリですが 使われてるコードをいじる機会があったので色々使ってみた感想です
簡単に言うと React ぽくなくなったり 単純なフォームじゃなくなると useEffect だらけで辛くなるので そんなに積極的に使いたいものではないです
react-hook-form を使うと 内部で state を管理してくれて 再レンダリングを防げます
各 input が props ではなくコンテキスト内の form から値を取得するので 親の再レンダリングが不要になり 変更があった input のコンポーネント内だけが再レンダリングされます
表示内容が多いページで memo なしだと入力ごとに再レンダリングされる範囲が広くなり 重たくなったりしますが それを防げます
フォームの制御が特になく 項目が多いだけのフォームだと楽に使えて便利です
しかし 入力値の変化に応じてあちこちを変更する場合は面倒になってきます
みたいに callback を指定する watch を使い form の値や state を更新していくことになります
useEffect が必要です
更新された場所の名前はわかりますが 更新前の値はわかりません
変化した内容に応じて処理を変えたいなら自分で前の値を保持する仕組みを作っておかないといけません
また イベントハンドラ的な部分での処理ではなく render 関数中の処理で現在の form の値を使いたいなら 名前指定で watch を使います
この場合はどれが変わったのかわかりません
また 変わるたびに この watch を使ったコンポーネントが再レンダリングされるので useState に入れているのと変わらなくなります
ものによっては form の値のほとんどを watch に入れることになり useState と対して変わらないってこともあります
watch で取得した値を使うのが hook だと仕方ないですが JSX 部分なら input 以外でも form を参照するようにしてしまうのもありです
input のように controller を使って値を取得すれば watch にする必要がなく コンポーネント全体の再レンダリングを減らせます
input に使う以外の form とは関係ない値でも form に入れてしまい state にせず form で管理するのもありかもです
そうすれば form の処理だけに統一できますし 名前指定の watch を減らせます
それに コンポーネント間の共有でコンテキストのようにも使えます
親コンポーネントで form に関数を入れて 深い部分のコンポーネントでそれを参照します
ただしあくまでライブラリとしては form なのでこの使い方はいいのだろうか?という気はします
想定されたことを外れると将来的なアップデートで面倒な目に合うのは十分考えられます
そういう使い方のものを別に用意したほうがいいのかもという気はしてます
深い部分で watch を使う注意点ですが 使ったコンポーネントだけが再レンダリングされそうに見えてしまいますがそうではないです
React の仕組み的にそういう手段はなく useForm を使ったコンポーネントが再レンダリングされます
その結果 その子孫コンポーネントも再レンダリングされてそうみえるだけです
こういうことをしてみるとわかりやすいです
Parent で useForm して watch を Child に渡します
Child で watch していて その input を書き換えると 「render Parent」 もログに表示されます
Parent コンポーネントも再レンダリングされています
また Child をメモした版の MemoChild を使うと props の control, name, watch には変更がないため MemoChild コンポーネントは再レンダリングされません
結果 Parent は再レンダリングされるのに MemoChild は再レンダリングされず 「render Child c」 はログに表示されません
画面上でも input の現在の状態を表示しているのですが ここが更新されず初期状態のままです
また props や state を見て ちょっと加工するというだけでも useEffect で setValue が必要になります
普通の React なら render 関数内で計算したり JSX 内に式を入れるくらいで済むことのために useEffect が増えていくのは見通しが悪くなるので不満です
さらに問題になるのはあちこちで同じ値を更新したいときです
useEffect やイベントハンドラの中で setValue することになりますが 特に useEffect だと呼び出し順がわかりづらいです
あちこちのコンポーネントから setValue されるとどういう順で呼び出されて値が変わるのかデバッグしづらく 思い通り動かないことがけっこうありました
通常の React なら useEffect は極力なくせて render 関数内での処理です
親から子の順ですし レンダリングごとにイミュータブルのはずなので 困ることは少ないです
完全に独立した入力項目で 初期値以外は外部から変更されることがないような場合はシンプルに書けて良さそうですが 制御する部分が多いと不満点が目立ちます
やっぱり 素の React で state 管理してる方が見やすくていい気がしますね
簡単に言うと React ぽくなくなったり 単純なフォームじゃなくなると useEffect だらけで辛くなるので そんなに積極的に使いたいものではないです
react-hook-form を使うと 内部で state を管理してくれて 再レンダリングを防げます
各 input が props ではなくコンテキスト内の form から値を取得するので 親の再レンダリングが不要になり 変更があった input のコンポーネント内だけが再レンダリングされます
表示内容が多いページで memo なしだと入力ごとに再レンダリングされる範囲が広くなり 重たくなったりしますが それを防げます
フォームの制御が特になく 項目が多いだけのフォームだと楽に使えて便利です
しかし 入力値の変化に応じてあちこちを変更する場合は面倒になってきます
useEffect(() => {
const subscription = watch((values, { name, type }) => {
if (name === "foo") {
setValue("bar", values.foo)
} else if (name === "bar") {
setState(values.bar)
}
})
return () => subscription .unsubscribe()
}, [watch])
みたいに callback を指定する watch を使い form の値や state を更新していくことになります
useEffect が必要です
更新された場所の名前はわかりますが 更新前の値はわかりません
変化した内容に応じて処理を変えたいなら自分で前の値を保持する仕組みを作っておかないといけません
また イベントハンドラ的な部分での処理ではなく render 関数中の処理で現在の form の値を使いたいなら 名前指定で watch を使います
const [foo, bar] = watch(["foo", "bar"])
この場合はどれが変わったのかわかりません
また 変わるたびに この watch を使ったコンポーネントが再レンダリングされるので useState に入れているのと変わらなくなります
ものによっては form の値のほとんどを watch に入れることになり useState と対して変わらないってこともあります
watch で取得した値を使うのが hook だと仕方ないですが JSX 部分なら input 以外でも form を参照するようにしてしまうのもありです
input のように controller を使って値を取得すれば watch にする必要がなく コンポーネント全体の再レンダリングを減らせます
input に使う以外の form とは関係ない値でも form に入れてしまい state にせず form で管理するのもありかもです
そうすれば form の処理だけに統一できますし 名前指定の watch を減らせます
それに コンポーネント間の共有でコンテキストのようにも使えます
親コンポーネントで form に関数を入れて 深い部分のコンポーネントでそれを参照します
ただしあくまでライブラリとしては form なのでこの使い方はいいのだろうか?という気はします
想定されたことを外れると将来的なアップデートで面倒な目に合うのは十分考えられます
そういう使い方のものを別に用意したほうがいいのかもという気はしてます
深い部分で watch を使う注意点ですが 使ったコンポーネントだけが再レンダリングされそうに見えてしまいますがそうではないです
React の仕組み的にそういう手段はなく useForm を使ったコンポーネントが再レンダリングされます
その結果 その子孫コンポーネントも再レンダリングされてそうみえるだけです
こういうことをしてみるとわかりやすいです
const Input = ({ control, name }) => {
const { field } = useController({ control, name })
return <input {...field} />
}
const Child = ({ control, watch, name }) => {
const value = watch(name)
console.log("render Child", name)
return (
<div>
<div>input value: {value}</div>
<Input control={control} name={name} />
</div>
)
}
const MemoChild = React.memo(Child)
const Parent = () => {
const { watch, control } = useForm({ defaultValues: { a: "1", b: "2", c: "3" } })
console.log("render Parent")
return (
<div>
<Input control={control} name="a" />
<hr />
<Child control={control} name="b" watch={watch} />
<hr />
<MemoChild control={control} name="c" watch={watch} />
</div>
)
}
Parent で useForm して watch を Child に渡します
Child で watch していて その input を書き換えると 「render Parent」 もログに表示されます
Parent コンポーネントも再レンダリングされています
また Child をメモした版の MemoChild を使うと props の control, name, watch には変更がないため MemoChild コンポーネントは再レンダリングされません
結果 Parent は再レンダリングされるのに MemoChild は再レンダリングされず 「render Child c」 はログに表示されません
画面上でも input の現在の状態を表示しているのですが ここが更新されず初期状態のままです
また props や state を見て ちょっと加工するというだけでも useEffect で setValue が必要になります
普通の React なら render 関数内で計算したり JSX 内に式を入れるくらいで済むことのために useEffect が増えていくのは見通しが悪くなるので不満です
さらに問題になるのはあちこちで同じ値を更新したいときです
useEffect やイベントハンドラの中で setValue することになりますが 特に useEffect だと呼び出し順がわかりづらいです
あちこちのコンポーネントから setValue されるとどういう順で呼び出されて値が変わるのかデバッグしづらく 思い通り動かないことがけっこうありました
通常の React なら useEffect は極力なくせて render 関数内での処理です
親から子の順ですし レンダリングごとにイミュータブルのはずなので 困ることは少ないです
完全に独立した入力項目で 初期値以外は外部から変更されることがないような場合はシンプルに書けて良さそうですが 制御する部分が多いと不満点が目立ちます
やっぱり 素の React で state 管理してる方が見やすくていい気がしますね
React の hook は便利なんだけど 作るものが大きく複雑になってくるとコンポーネントも複雑になってくる
やっぱり props として受け取った状態から HTML を作るだけ のコンポーネントのほうがスッキリしていて好き
そうするためにコンポーネントを 2 段にして外側では hook を使ってデータを取得したり加工したりするだけで そのデータを内側コンポーネントの props として渡す
内側コンポーネントは hook は使わず 受け取った props から画面を作る JSX を作って返す
スッキリはするけど長くなるのが面倒
それにこの感じ Redux が流行ってた頃に見かけた作り方に近そう
自分でそういうのは作ってないのであまり詳しくはないけど プレゼンテーションコンポーネント?みたいな名前がついてて ストアと通信してデータを受け取る部分と 副作用を持たない props から JSX を作るだけのコンポーネントに分けていたと思う
hook になってもそれでいいのかな と思ったけど こう分けるのは今では推奨しない みたいなのも見かけた
直接 hook を使えばいいから らしいけど使うとコンポーネントがごちゃごちゃしてくるし テストもしづらくなる
useMemo や useCallback はパフォーマンスの都合なもので なくても動作は変わらないからどっちでもいいけど useEffect は入るとかなり複雑化するから コンポーネント外に持っていきたい
useState は関数の引数として状態と更新関数を受け取るのと同じと聞くこともあるけど リセットしたいときとか テスト時のダミーデータを渡すことを考えたらやっぱり違うかなって思う
WebComponents だと標準の HTML 要素みたいなものとして考えればいいので内部で状態を持って外部通信もしてっていうのはありだと思う
img タグとか画像を取得して表示してるし
でも React の考え方だと 親が子のメソッド呼び出したり 自由に子の状態を取り出したりしない
親で最初から持っていて子に渡すだけ
その考え方だと コンポーネント内で外部通信とか状態を保持とか色々しないほうがいいようにも思う
とりあえず 外部通信したりアプリ固有のロジックでデータを変換したり計算したりみたいのをコンポーネントの中じゃなくて外でやってコンポーネントはその結果を受け取るだけにしたい
やっぱり props として受け取った状態から HTML を作るだけ のコンポーネントのほうがスッキリしていて好き
そうするためにコンポーネントを 2 段にして外側では hook を使ってデータを取得したり加工したりするだけで そのデータを内側コンポーネントの props として渡す
内側コンポーネントは hook は使わず 受け取った props から画面を作る JSX を作って返す
スッキリはするけど長くなるのが面倒
それにこの感じ Redux が流行ってた頃に見かけた作り方に近そう
自分でそういうのは作ってないのであまり詳しくはないけど プレゼンテーションコンポーネント?みたいな名前がついてて ストアと通信してデータを受け取る部分と 副作用を持たない props から JSX を作るだけのコンポーネントに分けていたと思う
hook になってもそれでいいのかな と思ったけど こう分けるのは今では推奨しない みたいなのも見かけた
直接 hook を使えばいいから らしいけど使うとコンポーネントがごちゃごちゃしてくるし テストもしづらくなる
useMemo や useCallback はパフォーマンスの都合なもので なくても動作は変わらないからどっちでもいいけど useEffect は入るとかなり複雑化するから コンポーネント外に持っていきたい
useState は関数の引数として状態と更新関数を受け取るのと同じと聞くこともあるけど リセットしたいときとか テスト時のダミーデータを渡すことを考えたらやっぱり違うかなって思う
WebComponents だと標準の HTML 要素みたいなものとして考えればいいので内部で状態を持って外部通信もしてっていうのはありだと思う
img タグとか画像を取得して表示してるし
でも React の考え方だと 親が子のメソッド呼び出したり 自由に子の状態を取り出したりしない
親で最初から持っていて子に渡すだけ
その考え方だと コンポーネント内で外部通信とか状態を保持とか色々しないほうがいいようにも思う
とりあえず 外部通信したりアプリ固有のロジックでデータを変換したり計算したりみたいのをコンポーネントの中じゃなくて外でやってコンポーネントはその結果を受け取るだけにしたい
(1) foo.js を読み込んで foo.js が foo/ 以下のモジュールを読み込む
(2) foo/index.js を読み込んで foo/index.js が foo/ 以下のモジュールを読み込む
CJS なら (2) が相性がいい
フォルダ名の foo を require すれば自動で index.js を補完してくれる
もとは foo.js 単体であとからモジュール分けするときに foo で require していれば読み込む側のコードは変更しなくていい
それに foo に関係するモジュールはすべて foo フォルダに入ってる
だけど ESM は CJS みたいな補完はなくて完全なファイル名が必要
どのモジュールをロードしているのかわかりやすくはあるけど import に foo/index.js のように書かないといけなくなる
foo.js からモジュール分けすると使うところの変更も必要
だから ESM なら (1) 形式にしたほうがいいのかなと最近思ってたりする
foo.js が foo/ を単純にインポートしてエクスポートするだけならまだいいけどここでも処理を入れると foo フォルダに入っていて欲しくなる
foo.js
foo/file1.js
foo/file2.js
(2) foo/index.js を読み込んで foo/index.js が foo/ 以下のモジュールを読み込む
foo/index.js
foo/file1.js
foo/file2.js
CJS なら (2) が相性がいい
フォルダ名の foo を require すれば自動で index.js を補完してくれる
もとは foo.js 単体であとからモジュール分けするときに foo で require していれば読み込む側のコードは変更しなくていい
それに foo に関係するモジュールはすべて foo フォルダに入ってる
だけど ESM は CJS みたいな補完はなくて完全なファイル名が必要
どのモジュールをロードしているのかわかりやすくはあるけど import に foo/index.js のように書かないといけなくなる
foo.js からモジュール分けすると使うところの変更も必要
だから ESM なら (1) 形式にしたほうがいいのかなと最近思ってたりする
foo.js が foo/ を単純にインポートしてエクスポートするだけならまだいいけどここでも処理を入れると foo フォルダに入っていて欲しくなる
Microsoft のドキュメントのページの URL が変わってることに気づきました
昔の見づらい頃から少し見やすくなった頃に docs というドメインになってました
https://docs.microsoft.com
これがいつの間にか learn に変わっていて docs でアクセスしたときに こっちにリダイレクトされるようになってました
https://learn.microsoft.com
C# やオフィスや fluent ui や WSL など Microsoft 製のもの全般で変わってます
ドキュメントなら docs でいい気もしますし 頻繁に変えるのはやめてほしいですね
過去に保存していた microsoft.com ドメインのリンクを確認していると support.microsoft.com でも一部 learn.microsoft.com にリダイレクトされてるのがありました
昔の見づらい頃から少し見やすくなった頃に docs というドメインになってました
https://docs.microsoft.com
これがいつの間にか learn に変わっていて docs でアクセスしたときに こっちにリダイレクトされるようになってました
https://learn.microsoft.com
C# やオフィスや fluent ui や WSL など Microsoft 製のもの全般で変わってます
ドキュメントなら docs でいい気もしますし 頻繁に変えるのはやめてほしいですね
過去に保存していた microsoft.com ドメインのリンクを確認していると support.microsoft.com でも一部 learn.microsoft.com にリダイレクトされてるのがありました
標準の履歴画面だとエクスポート機能は無い
検索はあるけど 「http://」 で http のサイト一覧が出ることはなく全件ヒットみたいな動き
単純に URL に入力内容が含まれるかではなく色々内部でやってそう
一応クオートで囲めばそれっぽくはなったけど 生のデータをもとに自分でフィルタ処理をしたい
履歴画面で devtools を開いてみたけど localStorage とかには保存されてない
ソースコードを見ると chrome.send で Edge 側で用意した非公開 API を呼び出してそう
これを無理に使うよりは拡張機能の機能で取得したほうが楽そうなので拡張機能を使う
適当なフォルダに manifest.json を準備
同じフォルダに popup.html もつくる
「edge://extensions/」 を開いて 開発者モードを有効にして このフォルダを選択
ボタンが追加されてるのでそれをクリックしてポップアップウィンドウを開く
ポップアップウィンドウで devtools を開く
あとは この devtools のコンソールで
みたいな感じで好きにフィルタできる
text は必須なので空文字を入れて絞り込みせずに持ってきて 配列の filter メソッドで処理してる
JavaScript で自由に処理できるので CSV 形式で保存なども簡単にできる
検索はあるけど 「http://」 で http のサイト一覧が出ることはなく全件ヒットみたいな動き
単純に URL に入力内容が含まれるかではなく色々内部でやってそう
一応クオートで囲めばそれっぽくはなったけど 生のデータをもとに自分でフィルタ処理をしたい
履歴画面で devtools を開いてみたけど localStorage とかには保存されてない
ソースコードを見ると chrome.send で Edge 側で用意した非公開 API を呼び出してそう
これを無理に使うよりは拡張機能の機能で取得したほうが楽そうなので拡張機能を使う
適当なフォルダに manifest.json を準備
{
"manifest_version": 3,
"name": "popup",
"version": "1.0",
"action": {
"default_popup": "popup.html"
},
"permissions": ["history"]
}
同じフォルダに popup.html もつくる
<h1>popup</h1>
「edge://extensions/」 を開いて 開発者モードを有効にして このフォルダを選択
ボタンが追加されてるのでそれをクリックしてポップアップウィンドウを開く
ポップアップウィンドウで devtools を開く
あとは この devtools のコンソールで
const items = await chrome.history.search({ startTime: 0, maxResults: 10000, text: "" })
items.filter(item => item.url.includes("http://"))
.map(item => `${new Date(item.lastVisitTime).toLocaleString()} ${item.url} ${item.title}`)
みたいな感じで好きにフィルタできる
text は必須なので空文字を入れて絞り込みせずに持ってきて 配列の filter メソッドで処理してる
JavaScript で自由に処理できるので CSV 形式で保存なども簡単にできる
ライブラリがちゃんと動いてくれなくて 内部挙動調べるために console.log すると実行順がよくわからないことになってて React だけでの動きを調べたメモ
コールバックタイプの setState を使って 同期的に複数回呼び出されていたので それだけの動きを確認するためにこんな感じのページを用意
この画面を開いてボタンをクリックしたときのログは
/// を書いてる行は React の devtools を入れてると薄く表示されるログ
もう一度ボタンを押すと少し変わって
最初の 1 を見ると同期的に実行されてるようにみえるけど 2, 3 を見るとコールバックは即時呼び出されずあとになって呼び出されてるのがわかる
薄く表示されるのは Strict モードで 2 回レンダー関数が呼び出されるときに 2 回目の実行中に呼び出されたものだったと思うけど そんなタイミングで実行されてるの?
コンポーネントの関数の最初に console.log を入れて試してみると本当にそんなタイミングで呼び出されてた
二回目のボタンでは setState の 1 より先に 2 や 3 の pre が呼び出されているのも気になる
どっち先かは運次第のランダムかなと思ったけどリロードして試すと再現性がある
薄いところだけ無視すると一回目は 2 と 3 だけ消えて 1 は残るという変な状態になるから console.log デバッグのときは Strict モードを一時的に無効にしたほうがわかりやすいかも
ちなみに原因になったライブラリは props で受け取った値が変わったときに state を更新するために useMemo + setState するとか変なことしてるのが多くて色々辛かった
みたいなの
メモの中で副作用起こさないでほしいし useEffect か ref に保持して if 文でやってほしい
コールバックタイプの setState を使って 同期的に複数回呼び出されていたので それだけの動きを確認するためにこんな感じのページを用意
const App = () => {
const [state, setState] = useState(0)
const onClick = () => {
console.log("ONCLICK")
console.log("pre setState(1)")
setState(prev => {
console.log("setState(1)")
return 1
})
console.log("pre setState(2)")
setState(prev => {
console.log("setState(2)")
return 2
})
console.log("pre setState(3)")
setState(prev => {
console.log("setState(3)")
return 3
})
}
return <button onClick={onClick}>{state}</button>
}
この画面を開いてボタンをクリックしたときのログは
ONCLICK
pre setState(1)
setState(1)
pre setState(2)
pre setState(3)
setState(2)
setState(3)
setState(2) ///
setState(3) ///
/// を書いてる行は React の devtools を入れてると薄く表示されるログ
もう一度ボタンを押すと少し変わって
ONCLICK
pre setState(1)
pre setState(2)
pre setState(3)
setState(1)
setState(2)
setState(3)
setState(1) ///
setState(2) ///
setState(3) ///
最初の 1 を見ると同期的に実行されてるようにみえるけど 2, 3 を見るとコールバックは即時呼び出されずあとになって呼び出されてるのがわかる
薄く表示されるのは Strict モードで 2 回レンダー関数が呼び出されるときに 2 回目の実行中に呼び出されたものだったと思うけど そんなタイミングで実行されてるの?
コンポーネントの関数の最初に console.log を入れて試してみると本当にそんなタイミングで呼び出されてた
二回目のボタンでは setState の 1 より先に 2 や 3 の pre が呼び出されているのも気になる
どっち先かは運次第のランダムかなと思ったけどリロードして試すと再現性がある
薄いところだけ無視すると一回目は 2 と 3 だけ消えて 1 は残るという変な状態になるから console.log デバッグのときは Strict モードを一時的に無効にしたほうがわかりやすいかも
ちなみに原因になったライブラリは props で受け取った値が変わったときに state を更新するために useMemo + setState するとか変なことしてるのが多くて色々辛かった
useMemo(() => {
setState(value_prop)
}, [value_prop])
みたいなの
メモの中で副作用起こさないでほしいし useEffect か ref に保持して if 文でやってほしい
このコードを実行したときの出力(プロパティ順)はどうなるでしょうか?
答えはこうなります
bar は最後に来ず obj を展開したときの最初の場所です
と書いた場合と同じです
この仕組み 順番に意味を持たせたいときに良くも悪くもあるんですよね
この場合は bar は常に上書きするもので obj.bar はいらないものです
なのにそれのせいで順番が決まってしまいます
オブジェクトで順番は無いものと考えるべきという主張もありますが 必要になるときもあるものです
上書きするものを除外したオブジェクトを作ればできなくはないものの面倒がありますし 数が多いと書くのが面倒です
関数を用意しておけば書く場所は楽になりますが こんなことしないといけないのかって気持ちになります
ただ 今の動きで助かってる部分もあるのでデフォルト挙動が変わってほしいかというと難しいところです
localStorage などに JSON 文字列を保存して それと現状に差分があるかを確認したいときなんかは今のほうが嬉しいです
JSON 文字列比較だとプロパティ順の違いで不一致になるので順番が変わってほしくないです
イミュータブルオブジェクトとして扱いたいときは上に書いたような方法で obj2 を作るのでこれで毎回順番が変わるとチェックが面倒になります
const obj = { foo: 1, bar: 2, baz: 3 }
const obj2 = { ...obj, bar: 4 }
console.log(JSON.stringify(obj2, null, " "))
答えはこうなります
{
"foo": 1,
"bar": 4,
"baz": 3
}
bar は最後に来ず obj を展開したときの最初の場所です
const obj2 = {}
obj2.foo = 1
obj2.bar = 2
obj2.baz = 3
obj2.bar = 4
と書いた場合と同じです
この仕組み 順番に意味を持たせたいときに良くも悪くもあるんですよね
この場合は bar は常に上書きするもので obj.bar はいらないものです
なのにそれのせいで順番が決まってしまいます
オブジェクトで順番は無いものと考えるべきという主張もありますが 必要になるときもあるものです
上書きするものを除外したオブジェクトを作ればできなくはないものの面倒がありますし 数が多いと書くのが面倒です
const obj = { foo: 1, bar: 2, baz: 3 }
const { bar, ...rest } = obj
const obj2 = { ...rest, bar: 4 }
console.log(JSON.stringify(obj2, null, " "))
{
"foo": 1,
"baz": 3,
"bar": 4
}
関数を用意しておけば書く場所は楽になりますが こんなことしないといけないのかって気持ちになります
const merge = (...objs) => {
const result = {}
for (const obj of objs) {
for (const [k, v] of Object.entries(obj)) {
if (result.hasOwnProperty(k)) {
delete result[k]
}
result[k] = v
}
}
return result
}
const obj = { foo: 1, bar: 2, baz: 3 }
const obj2 = merge(obj, { bar: 4 })
console.log(JSON.stringify(obj2, null, " "))
{
"foo": 1,
"baz": 3,
"bar": 4
}
ただ 今の動きで助かってる部分もあるのでデフォルト挙動が変わってほしいかというと難しいところです
localStorage などに JSON 文字列を保存して それと現状に差分があるかを確認したいときなんかは今のほうが嬉しいです
JSON 文字列比較だとプロパティ順の違いで不一致になるので順番が変わってほしくないです
イミュータブルオブジェクトとして扱いたいときは上に書いたような方法で obj2 を作るのでこれで毎回順番が変わるとチェックが面倒になります
WPF の XAML とか WebComponents の CustomElement を見てると設定のようなものを XML(HTML) のタグで書けます
子要素としてのタグを親要素にとっての設定として扱えます
DOM だと JavaScript で読み取って解析できるので設定と渡す方法として使うこともできます
しかし JSX は React など UI を作るライブラリ用のもので それを使うユーザーのためのものじゃないです
JSX で作られた React の Element などは基本的に中の構造を直接触るべきものじゃないですし それを解析して使うのは一般的でないです
無理に使っても仕様として公開されてるものではないのでアップデートで頻繁に変わる可能性もあります
JSX では props としてオブジェクトなど JavaScript の式を書けるので関数やオブジェクトを渡すのが一般的です
ただこの辺に統一感のなさを感じます
それなら タグ(コンポーネント)や children を含む props も全部オブジェクトとして書くのもありなのかもと思ったり
そう考えると JSX のような記法を導入せず 直接プログラムで JSX 相当のものを書く Flutter の考え方もありなのかもと思ったり
子要素としてのタグを親要素にとっての設定として扱えます
DOM だと JavaScript で読み取って解析できるので設定と渡す方法として使うこともできます
しかし JSX は React など UI を作るライブラリ用のもので それを使うユーザーのためのものじゃないです
JSX で作られた React の Element などは基本的に中の構造を直接触るべきものじゃないですし それを解析して使うのは一般的でないです
無理に使っても仕様として公開されてるものではないのでアップデートで頻繁に変わる可能性もあります
JSX では props としてオブジェクトなど JavaScript の式を書けるので関数やオブジェクトを渡すのが一般的です
ただこの辺に統一感のなさを感じます
それなら タグ(コンポーネント)や children を含む props も全部オブジェクトとして書くのもありなのかもと思ったり
そう考えると JSX のような記法を導入せず 直接プログラムで JSX 相当のものを書く Flutter の考え方もありなのかもと思ったり
個別に扱うことがないただの選択肢なら数字だけでもいいかなって思った
昔ながら感ある方法
選択肢それぞれに名前をつけて数値を対応させる
key-value 形式で数値と表示用ラベルを保持する
foo text のところは日本語だったりで画面に表示する用のテキスト
簡単なツールなどだと選択肢も簡単で英単語で表すのも簡単
だけど特殊な固有名詞とか選択肢を表す言葉が複雑なものになってくると 名前を考えるのも面倒
そもそも一般的な英語にしづらい固有名詞とかは日本語で書けばいいとも思ってるけど 他とのバランスとかで急に日本語を混ぜたくないとかもあって 英語にしたいときもある
こういうので数が多いと嫌になる
思ったのが 選択肢が特別な意味のないただの選択肢で これを選んだ場合に何かがあるとかが無いなら 最初から名前を用意しなければ名前を考える必要はないってこと
全体としてこれが何なのかを表してる foo_bar_options に当たる名前は必要だけど ここはそんなに難しいことがなくて面倒なのは選択肢なのでこれで十分
=== "1" は 1 が何かわからないのでしたくないけど それをすることがないなら名前もいらない
React で select を作るときもこういう感じでいい
昔ながら感ある方法
const FOO = 1
const BAR = 2
export const foo_bar_options = {
[FOO]: "foo text",
[BAR]: "bar text"
}
選択肢それぞれに名前をつけて数値を対応させる
key-value 形式で数値と表示用ラベルを保持する
foo text のところは日本語だったりで画面に表示する用のテキスト
簡単なツールなどだと選択肢も簡単で英単語で表すのも簡単
だけど特殊な固有名詞とか選択肢を表す言葉が複雑なものになってくると 名前を考えるのも面倒
そもそも一般的な英語にしづらい固有名詞とかは日本語で書けばいいとも思ってるけど 他とのバランスとかで急に日本語を混ぜたくないとかもあって 英語にしたいときもある
こういうので数が多いと嫌になる
思ったのが 選択肢が特別な意味のないただの選択肢で これを選んだ場合に何かがあるとかが無いなら 最初から名前を用意しなければ名前を考える必要はないってこと
全体としてこれが何なのかを表してる foo_bar_options に当たる名前は必要だけど ここはそんなに難しいことがなくて面倒なのは選択肢なのでこれで十分
export const foo_bar_options = {
1: "foo text",
2: "bar text",
}
=== "1" は 1 が何かわからないのでしたくないけど それをすることがないなら名前もいらない
React で select を作るときもこういう感じでいい
import { foo_bar_options } from "./constants.js"
const Select = ({ options }) => {
const opts = Object.entries(options)
return (
<select defaultValue={opts[0][0]}>
{opts.map(([k, v]) => (
<option key={k} value={k}>{v}</option>
))}
</select>
)
}
const Component = () => {
return (
<div>
<Select options={foo_bar_options} />
</div>
)
}