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代替品の作成でコードを書いているときに起こったエラーについて、記録してみる。 ...

September 10, 2025

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>) } } それでとりあえず、解決状態だと思われる、アプリ自体の動作を重くしないレベルの実装にもっていくまでについて、簡易的に記録を取ろうと思い、この記事を作った。 ...

September 10, 2025

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

この記事を作った動機 単に自分用に、zustand の使い方を今 (2025/9/9) わかっている範囲で、書き出してみるだけ。 環境 Vite React TypeScript Tailwind CSS zustand 概要 まだ全然わかっていないが、私の今 (2025/9/9) イメージとしては、「zustand とは、react で言う状態変数の管理に関するライブラリの一つで、親 -> 子 コンポーネントという流れで状態変数を渡していく以外で、コンポーネント間の状態変数を共有、管理することができるもの。」という感じである。 使い方 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") // 何かしらのコード } 関係ありそうな記事 Hooks の宣言、定義で躓いたこと 参考にしたサイトとか Introduction - Zustand https://zustand.docs.pmnd.rs/getting-started/introduction (2025年9月9日) ChatGPT https://chatgpt.com/ (2025年9月9日) Rules of Hooks – React https://react.dev/warnings/invalid-hook-call-warning (2025年9月10日) javascript - Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement in React Hooks - Stack Overflow https://stackoverflow.com/questions/53472795/uncaught-error-rendered-fewer-hooks-than-expected-this-may-be-caused-by-an-acc (2025年9月10日)

September 9, 2025

変数

この記事を作った動機 全然まだ私は使い方とか概念をわかってないと思うが、とにかくよく使う hooks について記録を取りたいので、記事を書くだけ。思考の整理とかって感じで書いていて、普通に間違い混ざってると思うし、他の人に参考になるかは怪しい。 react とデータの関係性 React では、データが変更されると UI が連動して更新されたりするという仕組みになっているので、普通の変数ではなく、データのありように合わせて、状態変数と呼ばれるものを使い分ける必要がある。 環境 Vite React TypeScript Tailwind CSS zustand 普通の変数 概要 普通に TypeScript や JavaScript で宣言するような let や var などと書いて宣言する変数たちのことである。これら変数の内容は基本的に React が再描画をかけると内容が初期化されて元の再描画前のデータは消えてしまう。 これを防ぎ、UI 再描画をまたいで変数の値を保持するためには、状態変数を useState なり、 useRef なり宣言して利用する必要があると思われる。 export default function Acompornent(){ // 再描画時には、x:100 y:100 にいちいち初期化されて変更内容が失われる let windowPos = { x: 100, y: 100 } // こちらは再描画時には内容は失われない const [style,setStyle] = useState({ top:0, left:0 }) // このようなコードを書くと windowPos は setStyle されたあと再描画で内容が初期値に戻ってしまう addEventListener("mousemove",(event:MouseEvent) => { // 何かしらのコード windowPos.x = event.screenX windowPos.y = event.screenY // 何かしらのコード setStyle({ top: windowPos.x, left: windowPos.y }) }) return <div style={style} className="fixed">This is styled text.</div> } useState 概要 オーソドックスな状態変数で、データが更新されたとき、UI を再描画させたいという場合に使う。 ...

September 9, 2025

コンポーネント内にネストができる関数コンポーネント

この記事を作った動機 Vite + React + TypeScript の環境でコンポーネント"内部"になにか要素をネストする方法がわかんなかったので記録するだけ。ただ厳密のこのやり方で正しいかはわかっていないが、ChatGPT-5 が出したやり方がとりあえず動いたので記録することにした。今のところ公式ドキュメントとかで今回出すようなやりかたをしているのはまだ見つけられていない。 環境 Vite React TypeScript Tailwind CSS zustand ネストの仕方 ネストできる関数コンポーネントを単体で定義 import {type ReactNode} from "react" export default function Outside1({ children }:{ children:ReactNode }){ return <div className="flex flex-row bg-gray-700 h-[5rem] w-screen top-[5rem] left-0 fixed"> {children} </div> } ネストできる関数コンポーネントをコンポーネント内に定義 import {type ReactNode} from "react" export default function Acompornent(){ // ネストができる関数コンポーネントは必ずしも、このようにコンポーネント内に書いている必要はないと思う。 function Outside({ children }:{ children:ReactNode }){ return <div className="flex flex-row bg-gray-700 h-[5rem] w-screen top-0 left-0 fixed"> {children} </div> } return <div> <Outside> <p>This</p> <p>is</p> <p>children.</p> </Outside> <Outside1> <p>This</p> <p>is</p> <p>children.</p> </Outside1> </div> } 参考にしたサイトとか ChatGPT https://chatgpt.com/ (2025年9月8日) Quick Start – React https://react.dev/learn (2025年9月9日)

September 8, 2025

JavaScript の条件分岐でわかりにくいミスをした

この記事を作った動機 MMD Viewer を作っているときに、地味にあとから効いてきそうなミスをすることを発見したのでそれを記録するだけ。 問題点 if 文の条件判定の書き方を間違えると、条件判定をする代わりに、判定に使った変数にブール型の結果を代入してしまう。 間違っている例 問題の部分だけ if(zipName =! null){ // <---- 問題の箇所 // これでは、zipName の中身が true になってしまう。 全体像 // try to find zip file let zipName = null zipName = findZipFile(modelFile) // try to find pmx or pmd file let modelName = null modelName = findPmxAndPmdFileName(modelFile) let fileMap = new Map() if(zipName =! null){ // <---- 問題の箇所 // set modelName is required readZipForModelFile(modelFile,zipName,fileMap) alert("zip read function on development.") return } 間違っていない例 問題を修正した部分だけ if(zipName != null){ // <---- 問題の箇所 // この場合は、通常どうり zipName の中身が null でないか判定しているということになり、zipName 自体の中身は判定前と同様に保持される。 全体像 // try to find zip file let zipName = null zipName = findZipFile(modelFile) // try to find pmx or pmd file let modelName = null modelName = findPmxAndPmdFileName(modelFile) let fileMap = new Map() if(zipName != null){ // <---- 問題の箇所 // set modelName is required readZipForModelFile(modelFile,zipName,fileMap) alert("zip read function on development.") return } 関連の記事 MMD Viewer 参考にしたサイトとか 気づいたことを書いただけなので特になし。 ...

August 26, 2025

MMD Viewer

このページは、まだ未完成です。。。 nicotalk&キャラ素材配布所 http://www.nicotalk.com/charasozai_kt.html (2024年5月16日) ツール本体 モデルとテクスチャをまとめて指定 モーションファイルを指定(vmd) UTF-8 Shift-JIS ...

August 9, 2025

IPC の使い方

このページは、まだ未完成です。。。 nicotalk&キャラ素材配布所 http://www.nicotalk.com/charasozai_kt.html (2024年5月16日) この記事を作った動機 electron で何かアプリを作ろうかなと思ったとき、心理的障壁として、IPC によるやり取りがあったので、それについて、間違ってもいいのでとにかく自分なりにノートを作ろうと思っただけ。 IPC なにそれおいしいの? IPC(Inter Process Communication) とは、複数のプログラム間で、データをやり取りしたりするための仕組みの一つ。いろんな仕組みがある。ネットワークを使うとか特定のメモリ領域を共有するとか。。。 electron と何が関係あるの? 今の私の理解だと、electron は複数のプロセスからなっていて、単に一つのプログラムに対して、一つのプロセスが起動しているという感じではない。少なくとも、OS側の処理を担う、main と呼ばれているプロセスと、chromiumベースの描画用のプロセス(renderer.jsもここ)が存在している。 セキュリティの関係上、一つのプロセスとして全てが処理されておらず、分離されている。そのため、例えばファイル操作を実装したいとなったときなどには、main プロセスと、renderer.js などが動いている描画用プロセス間でデータをやり取りする必要がある。 そこで、データをやり取りする手段として、IPCがある。 やり方 以下は適当に、Using Preload Scripts | Electronあたりから、IPCを使って main プロセスと描画プロセスがやり取りするために必要であると思われる部分を、部分的に引っ張っていじくりまわして試したコードである。 main.js preload.js を指定 ipcMain.handle const { app, BrowserWindow, ipcMain } = require('electron/main') const path = require("node:path") const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }) win.loadFile('src/index.html') } function scandir(){ return ["aaa.txt","bbb.txt"] } app.whenReady().then(() => { createWindow() ipcMain.handle("ping",() => "pong") ipcMain.handle("scandir",scandir) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) preload.js renderer.js内での呼び出し方の決定 const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld("callme",{ ping: () => ipcRenderer.invoke("ping"), scandir: () => ipcRenderer.invoke("scandir") }) // renderer.js // let response = await window.callme.ping() 以下は、Using Preload Scripts | Electronの引用 ...

May 11, 2025