onCleanup
コンポーネントがアンマウントされるか Effect が再実行されるときに実行されるクリーンアップ関数を登録します。
基本的な使い方
import { onCleanup } from '@luna_ui/luna';
function Timer() {
const [count, setCount] = createSignal(0);
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// コンポーネントアンマウント時にクリーンアップ
onCleanup(() => {
clearInterval(interval);
});
return <p>Count: {count()}</p>;
}
onCleanup が実行されるタイミング
コンポーネント内
コンポーネントが DOM から削除されるときに実行:
function Parent() {
const [show, setShow] = createSignal(true);
return (
<>
<button onClick={() => setShow(s => !s)}>トグル</button>
<Show when={show()}>
<Child /> {/* 非表示時に onCleanup 実行 */}
</Show>
</>
);
}
function Child() {
onCleanup(() => {
console.log("Child unmounted!");
});
return <p>ここにいます</p>;
}
Effect 内
Effect が再実行または破棄される前に実行:
const [url, setUrl] = createSignal("/api/data");
createEffect(() => {
const currentUrl = url();
const controller = new AbortController();
fetch(currentUrl, { signal: controller.signal })
.then(res => res.json())
.then(setData);
// url が変更されたとき(次の fetch 前)にクリーンアップ実行
onCleanup(() => {
controller.abort();
});
});
一般的なユースケース
タイマー
function Countdown(props) {
const [remaining, setRemaining] = createSignal(props.seconds);
const interval = setInterval(() => {
setRemaining(r => {
if (r <= 0) {
clearInterval(interval);
return 0;
}
return r - 1;
});
}, 1000);
onCleanup(() => clearInterval(interval));
return <p>{remaining()} 秒</p>;
}
イベントリスナー
function WindowResize() {
const [size, setSize] = createSignal({
width: window.innerWidth,
height: window.innerHeight,
});
const handler = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", handler);
onCleanup(() => {
window.removeEventListener("resize", handler);
});
return <p>ウィンドウ: {size().width}x{size().height}</p>;
}
WebSocket 接続
function Chat(props) {
const [messages, setMessages] = createSignal([]);
const ws = new WebSocket(`wss://chat.example.com/${props.room}`);
ws.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
onCleanup(() => {
ws.close();
});
return (
<For each={messages()}>
{(msg) => <p>{msg.text}</p>}
</For>
);
}
よくある間違い
クリーンアップの忘れ
// 悪い: メモリリーク!
function BadComponent() {
setInterval(() => {
console.log("アンマウント後も実行中!");
}, 1000);
return <div>...</div>;
}
// 良い: 適切なクリーンアップ
function GoodComponent() {
const interval = setInterval(() => {
console.log("アンマウント時にクリーンアップ");
}, 1000);
onCleanup(() => clearInterval(interval));
return <div>...</div>;
}
試してみよう
以下を行うコンポーネントを作成:
マウント時に WebSocket 接続を開く
受信メッセージを表示
アンマウント時に接続を適切に閉じる
解答
function WebSocketDemo() {
const [messages, setMessages] = createSignal([]);
const [status, setStatus] = createSignal("connecting");
let ws;
onMount(() => {
ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = () => {
setStatus("connected");
};
ws.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};
ws.onerror = () => {
setStatus("error");
};
ws.onclose = () => {
setStatus("disconnected");
};
});
onCleanup(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
}
});
const sendMessage = () => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send("Hello!");
}
};
return (
<div>
<p>Status: {status()}</p>
<button onClick={sendMessage} disabled={status() !== "connected"}>
Send Hello
</button>
<ul>
<For each={messages()}>
{(msg) => <li>{msg}</li>}
</For>
</ul>
</div>
);
}
次へ
Islands アーキテクチャ → について学ぶ