2026/9/9-やったこと

この記事を作った動機 2026/9/9 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。 次に軽くやりたいことを記述 コントローラープロセス間のファイル書き込み衝突を回避する実装 Core拡張機能と、コントローラーにおいて、設定を伝搬するように実装 拡張モジュールにmainSysから共通で提供する関数(インターフェース)を引数として辞書型などにまとめ、モジュールに渡して呼び出せるようにする コントローラーのログを見えるように、マルチプロセッシングに関して調べる コントローラー間をマルチプロセッシングライブラリのIPC実装でつなげる pexpect の端末セッションや、マルチプロセッシングで独立したプロセスとして動くプロセスのそれぞれの出力は、独立したターミナルを立ち上げるようにする。GUI環境であれば、GUIのインストールされているターミナルエミュレーターを複数起動し、それぞれのセッションやプロセスに端末を1つずつ割り当て、ログの可読性向上を図る。 拡張機能を読み込み終わったあと、依存関係に関して不足がないか確認する処理を導入する。具体的には各拡張機能の依存関係リストに対して、読み込んだ拡張機能を線形探索で単純にそれぞれ照合して不足がないか確認する。 卒論の試作物から、フロントエンドをコピーしてきて、今回の試作物の DataServer の動作検証ができるように改変する。Core拡張機能の呼び出し名に対応させ、割り込みとコマンドに関して、それぞれが独立してwebsocket接続を持つようにする。 やったことの詳細 DataServer の起動オプションを追加 DataServer について、拡張機能を読み込むだけの動作と、読み込んだ拡張機能をもとに、DataServer を起動させようとすることを分けてユーザーが明示してそれぞれ操作できるようにするために、argparseライブラリによって以下のようになるように実装した。 以下は、argparseに関する実装が正しくできたかテストした様子である。引数に対してどう動作するか確かめたかったので、実際に拡張機能を読み込んだり、DataServer を起動する機能は一時的にコメントアウトしてテストした。何も出力がターミナルに出ていないとき、引数が受理され、正常に動作したことを示す。 python main.py --loadExtension # 何も出力しない python main.py --startServer # 何も出力しない python main.py --loadExtension --startServer #--loadExtension and --startServer cannot be set at the same time. python main.py --loadExtension aaa #usage: main.py [-h] [--loadExtension] [--startServer] #main.py: error: unrecognized arguments: aaa Core 拡張機能の機能を一時的に制限 卒論で書いた試作物のフロントエンドと今回試作している DataServer 間で一旦動作を確認したいため、すでに実装がある程度できており、卒論時点で動いていたモジュールに限定して動作するように実装を変更した。 変更前 { "name": "Core", "UUID": "589fe65d-639c-42a3-b395-fca2143afd75", "description": "Manage notebook and pages. Provide basic notebook interface.", "frontend":{ "modules": [] }, "DataServer":{ "CommandModules":[ "backend/commands/info.py", "backend/commands/notebookAndPage/createNotebook.py", "backend/commands/notebookAndPage/deleteNotebook.py", "backend/commands/notebookAndPage/getPageType.py", "backend/commands/notebookAndPage/pageInfo.py", "backend/commands/notebookAndPage/updatePage.py", "backend/commands/notebookAndPage/copyPage.py", "backend/commands/notebookAndPage/createPage.py", "backend/commands/notebookAndPage/deletePage.py", "backend/commands/notebookAndPage/movePage.py", "backend/commands/notebookAndPage/renamePage.py", "backend/commands/notebookAndPage/addTag.py", "backend/commands/notebookAndPage/createTag.py", "backend/commands/notebookAndPage/deleteTag.py", "backend/commands/notebookAndPage/getTagList.py", "backend/commands/notebookAndPage/queryTag.py", "backend/commands/notebookAndPage/removeTag.py", "backend/commands/notebookAndPage/fileAdd.py", "backend/commands/notebookAndPage/fileData.py", "backend/commands/notebookAndPage/fileDelete.py", "backend/commands/notebookAndPage/fileInfo.py" ], "InterruptModules":[ "backend/interrupts/newInfo.py", "backend/interrupts/updatePage.py", "backend/interrupts/updateTag.py" ], "TaskModules":[ "backend/tasks/pageCleaner.py", "backend/tasks/showPingMessage.py" ] }, "extensionDependencies":[ ] } ...

2026年9月8日

2026/9/2-やったこと

この記事を作った動機 2026/9/2 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。 次に軽くやりたいことを記述 まだ拡張機能の読み込み部分は、とりあえず書いたという段階であり、パスやインポートの記述がおかしかったりするおそれがあり、デバッグが必要である。また、拡張機能を読み込んで DataServer が動作する事を確認するために、Core 拡張機能を完成させる必要がある。 Core 拡張機能を完成させるためには、主にパスの解決が正しく出来るように、拡張機能のモジュール呼び出しにおいて、どうやってmainSysと提携させるかが課題となっている。今具体的に考えている実装の一つとしては、モジュール呼び出し時に、DataServer のルートのパスや Notebook へのパスは、拡張機能ではない DataServer 側の組み込みの実装としてパスを解決しモジュールに伝える機構を実装することが考えられた。 また、mainSysについては、Python の仮想環境機能を使うことが考えられた。依存関係などをインストールするとき、既にインストールされている Python 環境を汚したりしてしまうのは避けたいと考えた。仮想環境は、存在しなければ拡張機能読み込みの段階で初期化し、存在すればそれが DataServer 起動時などに有効化されて使われるというような事を考えている。 今回の試作物としては、とりあえず DataServer が拡張機能を介して動作するということを最低限の目標としたいと考えた。そこまでできたら次は、3つ目のプロトタイプとして今度はフロントエンドをなんとかするというロードマップにしようかなと作業と進捗状況から思ったところであった。フロントエンドには、zustand のストアにおける値が異なるストア間で重複しているなど、SQLの文脈におけるデータ項目の重複と正規化の問題があり、その点がまず修正点としてあげられる。 DataServer において、WebSocket によるフロントエンドとの接続に関してセッションを管理したい。フロントエンドが接続してきたら、それぞれのフロントエンド自体をUUIDなどで識別して、正しく Command や Interrupt の経路がつながっているか管理することを考えている。 やったことの詳細 Core 拡張機能を実装 完全ではないが、とりあえず最低限の機能を DataServer に与えるために、Core 拡張機能を作成し、中身には前回のプロトタイプで存在していた、command、interrupt、task の実装を移植した。移植したと言っても配置して、manifest.jsonにまだその存在を記述しただけであり、まだ今回の試作物に適合させる実装がこれから必要である。 manifest.jsonの実装についても変更点があり、DataServerの項目についてCommandModules、InterruptModules、TaskModulesの3つに分けて、モジュールを記述するようにした。それぞれは、拡張機能のmanifest.jsonがあるフォルダを起点としたモジュールまでの相対パスが記述されている。それぞれのモジュールは1つのスクリプトで1つのモジュールという構成にしており、[moduleName].pyというモジュールであれば、内部にある関数名は必ず、def [moduleName](request,websocket):の形態を取る必要がある。パスの解決の問題により、今後モジュールが共通して受け付けるべき引数が増える可能性がある。 "DataServer":{ "CommandModules":[ "backend/commands/info.py", "backend/commands/notebookAndPage/createNotebook.py", "backend/commands/notebookAndPage/deleteNotebook.py", "backend/commands/notebookAndPage/getPageType.py", "backend/commands/notebookAndPage/pageInfo.py", "backend/commands/notebookAndPage/updatePage.py", "backend/commands/notebookAndPage/copyPage.py", "backend/commands/notebookAndPage/createPage.py", "backend/commands/notebookAndPage/deletePage.py", "backend/commands/notebookAndPage/movePage.py", "backend/commands/notebookAndPage/renamePage.py", "backend/commands/notebookAndPage/addTag.py", "backend/commands/notebookAndPage/createTag.py", "backend/commands/notebookAndPage/deleteTag.py", "backend/commands/notebookAndPage/getTagList.py", "backend/commands/notebookAndPage/queryTag.py", "backend/commands/notebookAndPage/removeTag.py", "backend/commands/notebookAndPage/fileAdd.py", "backend/commands/notebookAndPage/fileData.py", "backend/commands/notebookAndPage/fileDelete.py", "backend/commands/notebookAndPage/fileInfo.py" ], "InterruptModules":[ "backend/interrupts/newInfo.py", "backend/interrupts/updatePage.py", "backend/interrupts/updateTag.py" ], "TaskModules":[ "backend/tasks/pageCleaner.py", "backend/tasks/showPingMessage.py" ] }, manifest.jsonの他の変更点としては、別の拡張機能を依存関係として書けるようにした。具体的にどうやって異なる拡張機能間を上手く提携させるか、どういう課題があるかはみえていないが、とりあえず拡張機能の依存関係を記述できるようにした。例は以下のようなものである。 ...

2026年9月2日

2026/8/26-やったこと

この記事を作った動機 2026/8/26 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。 次に軽くやりたいことを記述 mainSysの各サブシステムがプロセスとして独立しているので、それらのプロセス間通信について、調べて実装する。commandから、interruptが呼び出せるようにするなど、実装を行う必要がある。threadingとは異なり、メモリ空間が共有されていない点に留意する必要がある。 またコマンドのモジュール読み込み部分については、extensionMoludes = {moduleFunc:[moduleFuncName],...}という形態で、前回の辞書型の実装を単に拡張される前提で書き換えただけであるが、それだとどのコマンドがどの拡張機能に属しているか管理が不十分になると思われる。これに対して考えられることとしては、前回の試作物のように、コード上の関数名と辞書型のキーの値を同じにするのではなく、キーの部分については識別性を上げるために、extensionName/moduleFuncNameのようにすることが考えられた。また同名の拡張機能のためには、extensionName-UUIDの一部/moduleFuncNameのようにして重複を防止することが考えられた。 また今回の実装では、WebScoket の経路が、commandとinterruptで分かれており、それぞれは混同されない仕組みとした。現状では使用されるポートが前回と異なりハードコードで決め打ちとなっている。commandのサブシステムに関してはlocalhost:50097に、interruptのサブシステムに関しては、localhost:50098にホスティングされる。 今回試作する DataServer を前回の試作物のフロントエンドに適用するには、前回の試作物のフロントエンドの装の調整が必要である。 やったことの詳細 主に以下の項目について実装を進めた mainSys の実装を進めた command、interrupt、taskについてそれぞれ、具体的な中身を記述した。その過程において、拡張機能を読み込みには、command、interrupt、taskのそれぞれにおいて、決まった関数の Import と extensionMoludes = {moduleFunc:[moduleFuncName],...} の設定を必要とし、DataServer 起動時にそれらが文字列として構築されることとした。 mainSys の起動をするためのコードの実装を進めた mainSysを起動するための、mainSys/main.pyでは、あくまでフロントエンドやデータ転送の機能のホスティングやcommand、interrupt、taskを常駐させるためにプロセスを生成したり、終了時の例外処理に特化させた。当初はここにモジュールの Import や読み込みは集約させようと思っていたが、それだと引数などとして command、interrupt、task などのコントローラにそれぞれ参照を渡したりする手間が発生することが分かったため、command、interrupt、task のコントローラのスクリプト群に直接組み込むことで、実装を単純にしようとした。 前回の試作品からいくつかのコードを移植 今回の試作物は、拡張機能によって成り立つシステムという事を想定しているため、それ以外の部分については前回の試作物から移植して調整したものを使っている。具体的には以下のような部分が移植されている。 WebSocket 接続を待ち受ける実装 NotImplementedResponse、malformedRequestResponse、notFound といった DataServer がコマンドの動作に関して常に最低限必要としているレスポンス実装 showJSONMessage、malformedRequestChecker、receiveLoop などといった、コンソールに DataServer の送受信内容を表示する関数や、受信データの確認、WebSocket の実装に関するヘルパー関数について、引き継いでいる。 研究に関係ありそうなブログ記事 今回は特になし。 参考にしたサイトとか 今回は特になし。

2026年8月26日

2026/8/19-やったこと

この記事を作った動機 2026/8/19 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。 次に軽くやりたいことを記述 とにかく中身の実装を進める。DataServer 周りをとにかく終わらせて、とりあえず卒論のフロントエンドに対して、DataServer を今回作る試作物に置き換えて動作するくらいにはしたい。 やったことの詳細 内部構造について 大まかには、DataServer 周りのシステムのディレクトリ構造を卒論時点のものとは大幅に変更した。また、スクリプトを配置して、具体的な内容が書き始められるように、擬似コード的な感じでスクリプトの各所にコメントを記述したりした。大きな変更点は、“具体的な実装"をなるべく含めないようにすることで、抽象度を上げ、データの器として DataServer を機能させるための下地を作ったという点である。また、拡張されるのは DataServer だけではなく、フロントエンドも対象であり、DataServer と整合性をもって動かす必要があるため、DataServer の一部としてフロントエンドを組み込んだ。 卒論時点の DataServer のディレクトリ配置 ├── commands │ ├── _index.py │ ├── fileAPI │ │ ├── fileAdd.py ... │ ├── info.py │ ├── notebookAndPage │ │ ├── copyPage.py │ │ ├── createNotebook.py │ │ ├── createPage.py ... │ ├── tag │ │ ├── addTag.py │ │ ├── createTag.py ... │ └── template.py ├── configure.py ├── controller.py ├── helper │ ├── _index.py │ ├── common.py │ ├── loadSettings.py │ └── netwrok.py ├── interrupts │ ├── controller.py │ ├── newInfo.py │ ├── updatePage.py │ └── updateTag.py ├── jobs │ ├── pageCleaner.py │ └── showPingMessage.py ├── main.py ├── requirements.txt ├── settings.json ├── tasks.py ... 今回の試作品のディレクトリ配置 ├── extensionLoader │ ├── common.py │ ├── dataserver.py │ ├── frontend.py │ ├── main.py │ └── template │ ├── dataserver.py │ └── frontend.tsx ├── extensions │ └── readme.md ├── frontend ├── main.py ├── mainSys │ ├── controller │ │ ├── command.py │ │ ├── interrupt.py │ │ └── task.py │ ├── hosting │ │ ├── fileTransfar.py │ │ └── frontendServe.py │ └── main.py └── settings.json 今回の試作物の構成について軽く説明 今回の試作品の各ディレクトリやファイル配置についての説明としては以下のとおりである。 ...

2026年8月19日

Date クラスで躓いたこと

この記事を作った動機 Date クラスの仕様で、引っかかった部分があるのでそのことについて記録をする。具体的には、getMonthメンバ関数を使おうとしたとき、思っているのとは違う結果になったというのがある。 内容 Date クラスの getMonth関数は、MDNに書かれているように、ゼロからスタートする値である。1月は、この関数の戻り値として、0 と表現され、12月は 11 として表現される。 The return value of getMonth() is zero-based, which is useful for indexing into arrays of months, for example: 具体的に引っかかったところ createDateStringという関数を作っていて、文字列として"YYYY/MM/DD"のような形式で今いつかということを返すようにしようとしていた時、月の部分が一つ前になっていることに気づいた。 createDateStringを実行した日を2025/10/29として、問題のあるコードと、そうでないコードの違いを以下に示す。 問題のコード 帰ってくる戻り値は、2025/9/29。 export function createDateString(){ // yyyy/mm/dd const currentDate = new Date() let dateString = "" dateString += String(currentDate.getFullYear()) + "/" dateString += String(currentDate.getMonth()) + "/" dateString += String(currentDate.getDate()) return dateString } 修正したコード 帰ってくる戻り値は、2025/10/29。 export function createDateString(){ // yyyy/mm/dd const currentDate = new Date() let dateString = "" dateString += String(currentDate.getFullYear()) + "/" dateString += String(currentDate.getMonth() + 1) + "/" dateString += String(currentDate.getDate()) return dateString } 参考にしたサイトとか Date - JavaScript | MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date (2025年10月29日)

2025年10月29日

Websocket を自動で再接続したい

この記事を作った動機 Websocket を使ったフロントエンドで、自動的に接続が途切れたら再接続するコードを書くときに躓いたので、その記録をする。 Websocket の接続に関するエラーハンドリング Websocket が接続失敗したときに、やらせたい処理がある場合は、try-catch は使えない。接続に関する問題に対処するには、コールバック関数として、“error"イベントが発火されたときに処理を登録する必要がある。 Websocket は、非同期で実行されるため try-catch では接続できなかったときのエラーを拾えないようである。 うまくいかない例 // この例だと、reconnect 関数が実行されることはない。この形だと、WebScoket クラスに渡したパラメータ異常しか拾えない。 const setWebsocket = useDatabaseStore.setState; try{ const websocket = new WebSocket("ws://localhost:50097") setWebsocket({ websocket: websocket }) }catch(e){ console.log(e) reconnect() } うまくいく例 const setWebsocket = useDatabaseStore.setState; const websocket = new WebSocket("ws://localhost:50097") websocket.onerror = () => { reconnect() } setWebsocket({ websocket: websocket }) const setWebsocket = useDatabaseStore.setState; const websocket = new WebSocket("ws://localhost:50097") websocket.addEventListener("error",reconnect) setWebsocket({ websocket: websocket }) 参考にしたサイトとか ChatGPT https://chatgpt.com/ (2025年9月29日) WebSocket: close イベント - Web API | MDN https://developer.mozilla.org/ja/docs/Web/API/WebSocket/close_event (2025年9月29日) WebSocket: error イベント - Web API | MDN https://developer.mozilla.org/ja/docs/Web/API/WebSocket/error_event (2025年9月29日) WebSocket: WebSocket() コンストラクター - Web API | MDN https://developer.mozilla.org/ja/docs/Web/API/WebSocket/WebSocket (2025年9月29日)

2025年9月29日

Hooks の宣言、定義で躓いたこと

この記事を作った動機 React で useState なり useEffect なり hooks と呼ばれている状態変数と関係があるものを使うときは厳し目の条件があるようで、それが原因で動かないということがあった。 そこでどこに気をつければ良さそうか、公式ドキュメントを見たり、調べてみたりして、自分なりの理解でとりあえず記録しようということになった。 環境 Vite React TypeScript Tailwind CSS zustand hooks とは use〇〇 という感じで定義されている React の API 群の事っぽい。現状の体感としては、状態変数に関係する React の API のことを包括的に hooks とよんでいるように見える。以下は hooks の例である。 useState useRef useEffect 状態変数管理ライブラリで使う関数の一部? zustand の例 // 定義 --------------------------------------------- // 定義 --------------------------------------------- import { useEffect, useState } from "react" import { create } from "zustand"; type DatabaseState = { websocket: WebSocket | null; serverIP: string | null; changeServer: (ip: string) => void; closeConnection: () => void; getWebsocket: () => WebSocket | null; }; // hooks 相当の部分の定義 export const useDatabaseStore = create<DatabaseState>((set, get) => ({ websocket: null, serverIP: "ws://localhost:50097", changeServer: (ip: string) => { set({ serverIP: ip }); }, closeConnection: () => { const ws = get().websocket; ws?.close(); set({ websocket: null }); }, getWebsocket: () => get().websocket, })); // 定義 --------------------------------------------- // 定義 --------------------------------------------- export function Acompornent(){ // hooks 相当のコード const websocket = useDatabaseStore((s) => s.websocket); // 何かしらのコード } use〇〇 から始まる APIを組み合わせた関数 // 定義 --------------------------------------------- // 定義 --------------------------------------------- // 上記の ”zustand の例” と同じ // 定義 --------------------------------------------- // 定義 --------------------------------------------- // useEffect の React API としての hooks や // useDatabaseStore の zustand (状態変数管理ライブラリの関数) としての hooks // を組み合わせてた 任意の定義の hooks の例 export function useDatabaseEffects() { const serverIP = useDatabaseStore((s) => s.serverIP); const setWebsocket = useDatabaseStore.setState; useEffect(() => { if (!serverIP) return; const ws = new WebSocket(serverIP); setWebsocket({ websocket: ws }); // return () => { // ws.close(); // }; }, [serverIP, setWebsocket]); } 気にしたほうが良さそうなこと コンポーネント内に hooks は書く コンポーネント内のなるべく先頭に hooks は書く コンポーネントの関数内のスコープからハズレたところには hooks は定義、宣言はできない。以下の部分が基本的にポイントな気がする。 if 文の中 loop 内 コールバック関数など、コンポーネント関数のスコープから外れるところ レンダリング部分内部 hooks で定義された状態変数の操作などは、コンポーネント関数内とかに限らず、コールバック内で呼び出したり、書き込んだり比較的自由にできる模様である。 // 何かしらのコード export default function Selector() { const websocket = useDatabaseStore((s) => s.websocket); const [visible,setVisible] = useState(false) const init = useRef(true) const toolbarAddTool = useToggleableStore((s) => s.addToggleable) // zustand (状態変数管理ライブラリ) も関係ある const [index, setIndex] = useState<Info>({ status: "init", errorMessage: "nothing", data: null, }); useEffect(() => { if (!websocket) { setIndex({ status: "error", errorMessage: "No data server connected to.", data: null, }); return; } const handleMessage = (event: MessageEvent) => { const result = JSON.parse(String(event.data)); console.log(result) if (!result.status.includes("error")) { setIndex(result); } else { setIndex({ status: result.status, errorMessage: result.errorMessage, data: null, }); } }; const whenOpened = () => { const request = JSON.stringify({ command: "info", data: null }); websocket.send(request); } websocket.addEventListener("message", handleMessage); websocket.addEventListener("open",whenOpened) // cleanup return () => { websocket.removeEventListener("message", handleMessage); }; }, [websocket]); // 条件分岐やループ、レンダリングや何かしらのコールバック関数内などで、 // hooks を宣言したり、定義することはできないという感じの模様 // しかし、hooks の定義で出てくる set〇〇 などを使ったりして、状態変数を操作したりなどはできる模様 if(init.current){ const toggleable:toggleable = { name: "Selector", setVisibility: setVisible, visibility:visible } toolbarAddTool(toggleable) init.current = false } // 何かしらのコード } エラーの発生と修正例 実際に私がOnenote代替品の作成でコードを書いているときに起こったエラーについて、記録してみる。 ...

2025年9月10日

React と addEventListener

この記事を作った動機 React で addEventListener を使ったら、再描画される事に、addEventListener されまくって、例えばマウスボタンが押されたというイベントが発火したときに、無数の同じ処理が走りまくるという事態になった。 これは以下のように、react の要素の一部として書いたときには、起こらなかった。 export function OverlayWindow({ children, arg }:{ children:ReactNode, arg:OverlayWindowArgs }){ // 何かしらのコード if(visible){ return (<div className={OverlayWindowContaierClassName} style={windowPosStyle}> <div className="windowHeader move bg-yellow-600 w-full h-[2rem] justify-center place-items-center align-middle text-center" onMouseDown={windowHandlers.mousedown} // addEventListener 相当 onTouchStart={windowHandlers.touchstart} // addEventListener 相当 > <div className="title h-[1rem] absolute text-white">{arg.title}</div> <div className="close size-[2rem] bg-red-700 ml-auto" onClick={() => {setVisible(false)}}></div> </div> <div className="content bg-gray-900 min-h-[5rem] w-full flex justify-center place-items-center align-middle text-center items-center"> {children} </div> </div>) } } それでとりあえず、解決状態だと思われる、アプリ自体の動作を重くしないレベルの実装にもっていくまでについて、簡易的に記録を取ろうと思い、この記事を作った。 ...

2025年9月10日

記事リスト

この記事を作った動機 単に、このブログの方針として、”自分が参照するノート”としてメインに掲げているところがある。それで、必ずしも “content/posts/idea/research/onenoteAlternativeCreation/” 配下に収まっているわけではなく、色んな所に関係のある記事が散らばっている。 それらをまとめるために、この記事にリンク集みたいな感じで、散らばってしまっている記事をまとめておこうと思った次第である。 記事リスト React コンポーネント内にネストができる関数コンポーネント 変数 Zustand 状態変数管理ライブラリ Hooks の宣言、定義で躓いたこと Websocket を自動で再接続したい TypeScript Date クラスで躓いたこと

2025年9月10日

Zustand 状態変数管理ライブラリ

この記事を作った動機 単に自分用に、zustand の使い方を今 (2025/9/9) わかっている範囲で、書き出してみるだけ。 環境 Vite React TypeScript Tailwind CSS zustand 概要 まだ全然わかっていないが、私の今 (2025/9/9) イメージとしては、「zustand とは、react で言う状態変数の管理に関するライブラリの一つで、親 -> 子 コンポーネントという流れで状態変数を渡していく以外で、コンポーネント間の状態変数を共有、管理することができるもの。」という感じである。 TODO 参照が変わらないと、再描画が起こらないことを書く。例えば、setter 内で、get()したものを直接変更し、set()のところに使うと、参照が同じのため、問題が起こる。 使い方 import import { create } from "zustand"; 宣言例 type DatabaseState = { websocket: WebSocket | null; serverIP: string | null; changeServer: (ip: string) => void; closeConnection: () => void; getWebsocket: () => WebSocket | null; }; export const useDatabaseStore = create<DatabaseState>((set, get) => ({ // 変数の初期化 websocket: null, serverIP: "ws://localhost:50097", // 変数を操作する関数群 changeServer: (ip: string) => { set({ serverIP: ip }); }, closeConnection: () => { const ws = get().websocket; ws?.close(); set({ websocket: null }); }, getWebsocket: () => get().websocket, })); データを呼び出し、利用する export function Acompornent(){ // 他の hooks と同様にコンポーネントのコードの先頭あたりに書く必要がある。 const websocket = useDatabaseStore((s) => s.websocket); console.log(websocket) // 何かしらのコード } 関数を呼び出す export function Acompornent(){ // 他の hooks と同様にコンポーネントのコードの先頭あたりに書く必要がある。 const changeServer = useDatabaseStore((s) => s.changeServer) changeServer("ws://localhost:88091") // 何かしらのコード } 躓いたこと イミュータブルの原則 zustand を使っているときに、どうやっても値を更新しているにも関わらず、再描画が正しくかからないという問題が起こった。これは、同じ参照を持つ変数を変更するという、“ミュータブル"な扱いをしたときに起こるようである。一度宣言された、作られた変数は原則変更しない(できない)というのが、React や zustand といった状態変数の概念の前提にはあるっぽいと私は最終的に考えた。 ...

2025年9月9日