生命周期 #
一、生命周期概述 #
1.1 Solid 的生命周期理念 #
与 React 不同,Solid 没有传统的生命周期钩子(如 componentDidMount、useEffect)。Solid 使用响应式系统来处理生命周期相关的逻辑。
text
┌─────────────────────────────────────────────────────────────┐
│ Solid vs React 生命周期 │
├─────────────────────────────────────────────────────────────┤
│ │
│ React │
│ ├── componentDidMount → 组件挂载后 │
│ ├── componentDidUpdate → 组件更新后 │
│ └── componentWillUnmount → 组件卸载前 │
│ │
│ Solid │
│ ├── onMount → 组件初始化时 │
│ ├── createEffect → 响应式更新 │
│ └── onCleanup → 清理资源 │
│ │
│ 关键区别: │
│ - Solid 组件只渲染一次 │
│ - 使用响应式系统处理更新 │
│ - 生命周期函数更简洁 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 生命周期函数 #
| 函数 | 说明 |
|---|---|
onMount |
组件挂载时执行 |
onCleanup |
组件卸载或 Effect 清理时执行 |
createEffect |
响应式副作用 |
二、onMount #
2.1 基本用法 #
onMount 在组件首次渲染到 DOM 后执行一次。
jsx
import { onMount } from 'solid-js';
function MyComponent() {
onMount(() => {
console.log('Component mounted');
});
return <div>Hello</div>;
}
2.2 DOM 访问 #
jsx
function InputFocus() {
let inputRef;
onMount(() => {
// DOM 已挂载,可以安全访问
inputRef.focus();
});
return (
<input ref={inputRef} type="text" />
);
}
2.3 数据获取 #
jsx
function UserProfile({ userId }) {
const [user, setUser] = createSignal(null);
const [loading, setLoading] = createSignal(true);
onMount(async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
});
return (
<Show when={!loading()} fallback={<p>Loading...</p>}>
<div>
<h1>{user()?.name}</h1>
<p>{user()?.email}</p>
</div>
</Show>
);
}
2.4 第三方库初始化 #
jsx
function Chart({ data }) {
let chartRef;
let chart;
onMount(() => {
// 初始化图表库
chart = new ChartLibrary(chartRef, {
type: 'line',
data: data
});
});
onCleanup(() => {
// 清理图表实例
chart?.destroy();
});
return <div ref={chartRef} class="chart"></div>;
}
2.5 onMount 与 createEffect 的区别 #
jsx
function Comparison() {
const [count, setCount] = createSignal(0);
// onMount: 只执行一次
onMount(() => {
console.log('Mounted once');
});
// createEffect: 每次 count 变化都执行
createEffect(() => {
console.log('Count changed:', count());
});
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count()}
</button>
);
}
三、onCleanup #
3.1 基本用法 #
onCleanup 在组件卸载或 Effect 重新执行前执行。
jsx
import { onCleanup } from 'solid-js';
function Timer() {
const [seconds, setSeconds] = createSignal(0);
onMount(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
onCleanup(() => {
clearInterval(interval);
console.log('Timer cleaned up');
});
});
return <p>Seconds: {seconds()}</p>;
}
3.2 事件监听清理 #
jsx
function MouseTracker() {
const [position, setPosition] = createSignal({ x: 0, y: 0 });
onMount(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
onCleanup(() => {
window.removeEventListener('mousemove', handleMouseMove);
});
});
return (
<p>
Position: {position().x}, {position().y}
</p>
);
}
3.3 在 Effect 中使用 #
jsx
function EffectCleanup() {
const [count, setCount] = createSignal(0);
createEffect(() => {
const timer = setTimeout(() => {
console.log('Count:', count());
}, 1000);
onCleanup(() => {
clearTimeout(timer);
});
});
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count()}
</button>
);
}
3.4 WebSocket 清理 #
jsx
function ChatRoom({ roomId }) {
const [messages, setMessages] = createSignal([]);
const [connected, setConnected] = createSignal(false);
createEffect(() => {
const ws = new WebSocket(`wss://example.com/rooms/${roomId()}`);
ws.onopen = () => setConnected(true);
ws.onclose = () => setConnected(false);
ws.onmessage = (e) => {
setMessages(prev => [...prev, JSON.parse(e.data)]);
};
onCleanup(() => {
ws.close();
setConnected(false);
});
});
return (
<div>
<p>Status: {connected() ? 'Connected' : 'Disconnected'}</p>
<ul>
<For each={messages()}>
{(msg) => <li>{msg.text}</li>}
</For>
</ul>
</div>
);
}
四、生命周期模式 #
4.1 初始化模式 #
jsx
function useInitialize(callback) {
onMount(callback);
}
function MyComponent() {
useInitialize(() => {
console.log('Component initialized');
});
return <div>Content</div>;
}
4.2 资源管理模式 #
jsx
function useResource(fetchFn) {
const [data, setData] = createSignal(null);
const [loading, setLoading] = createSignal(true);
const [error, setError] = createSignal(null);
onMount(async () => {
try {
const result = await fetchFn();
setData(result);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
});
return { data, loading, error };
}
function UserProfile({ userId }) {
const { data: user, loading, error } = useResource(
() => fetch(`/api/users/${userId}`).then(r => r.json())
);
return (
<Show when={!loading()} fallback={<p>Loading...</p>}>
<Show when={!error()} fallback={<p>Error: {error()}</p>}>
<h1>{user()?.name}</h1>
</Show>
</Show>
);
}
4.3 订阅模式 #
jsx
function useSubscription(eventEmitter, eventName) {
const [data, setData] = createSignal(null);
onMount(() => {
const handler = (newData) => setData(newData);
eventEmitter.on(eventName, handler);
onCleanup(() => {
eventEmitter.off(eventName, handler);
});
});
return data;
}
4.4 定时器模式 #
jsx
function useInterval(callback, delay) {
onMount(() => {
const id = setInterval(callback, delay);
onCleanup(() => clearInterval(id));
});
}
function Clock() {
const [time, setTime] = createSignal(new Date());
useInterval(() => setTime(new Date()), 1000);
return <p>{time().toLocaleTimeString()}</p>;
}
五、实际应用 #
5.1 表单自动保存 #
jsx
function AutoSaveForm({ initialData, onSave }) {
const [formData, setFormData] = createSignal(initialData);
createEffect(() => {
const timer = setTimeout(() => {
onSave(formData());
}, 1000);
onCleanup(() => clearTimeout(timer));
});
return (
<form>
<input
value={formData().name}
onInput={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
/>
<textarea
value={formData().content}
onInput={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
/>
</form>
);
}
5.2 媒体查询监听 #
jsx
function useMediaQuery(query) {
const [matches, setMatches] = createSignal(
window.matchMedia(query).matches
);
onMount(() => {
const mediaQuery = window.matchMedia(query);
const handler = (e) => setMatches(e.matches);
mediaQuery.addEventListener('change', handler);
onCleanup(() => mediaQuery.removeEventListener('change', handler));
});
return matches;
}
function ResponsiveComponent() {
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div>
{isMobile() ? 'Mobile View' : 'Desktop View'}
</div>
);
}
5.3 可见性检测 #
jsx
function useIntersectionObserver(ref, options = {}) {
const [isIntersecting, setIsIntersecting] = createSignal(false);
const [entry, setEntry] = createSignal(null);
onMount(() => {
const observer = new IntersectionObserver(
([entry]) => {
setIsIntersecting(entry.isIntersecting);
setEntry(entry);
},
options
);
if (ref) {
observer.observe(ref);
}
onCleanup(() => observer.disconnect());
});
return { isIntersecting, entry };
}
function LazyImage({ src, alt }) {
let imgRef;
const { isIntersecting } = useIntersectionObserver(
() => imgRef,
{ threshold: 0.1 }
);
return (
<img
ref={imgRef}
src={isIntersecting() ? src : ''}
alt={alt}
loading="lazy"
/>
);
}
5.4 键盘快捷键 #
jsx
function useKeyboardShortcut(key, callback, modifiers = {}) {
onMount(() => {
const handler = (e) => {
const matchCtrl = modifiers.ctrl ? e.ctrlKey : true;
const matchShift = modifiers.shift ? e.shiftKey : true;
const matchAlt = modifiers.alt ? e.altKey : true;
if (e.key === key && matchCtrl && matchShift && matchAlt) {
e.preventDefault();
callback();
}
};
window.addEventListener('keydown', handler);
onCleanup(() => window.removeEventListener('keydown', handler));
});
}
function Editor() {
const [content, setContent] = createSignal('');
useKeyboardShortcut('s', () => saveContent(content()), { ctrl: true });
useKeyboardShortcut('z', () => undo(), { ctrl: true });
return (
<textarea
value={content()}
onInput={(e) => setContent(e.target.value)}
/>
);
}
六、最佳实践 #
6.1 组织生命周期代码 #
jsx
function MyComponent() {
// 1. 状态声明
const [data, setData] = createSignal(null);
// 2. 生命周期
onMount(() => {
// 初始化逻辑
fetchData().then(setData);
// 清理逻辑紧跟其后
onCleanup(() => {
// 清理资源
});
});
// 3. Effects
createEffect(() => {
// 响应式逻辑
});
// 4. 渲染
return <div>{data()}</div>;
}
6.2 避免内存泄漏 #
jsx
// 好的做法:总是清理资源
function GoodExample() {
onMount(() => {
const interval = setInterval(() => {}, 1000);
const subscription = eventEmitter.subscribe(() => {});
onCleanup(() => {
clearInterval(interval);
subscription.unsubscribe();
});
});
}
// 不好的做法:忘记清理
function BadExample() {
onMount(() => {
setInterval(() => {}, 1000); // 内存泄漏
});
}
6.3 条件性清理 #
jsx
function ConditionalCleanup() {
let resource = null;
onMount(() => {
if (someCondition) {
resource = createResource();
}
onCleanup(() => {
if (resource) {
resource.destroy();
}
});
});
}
七、总结 #
7.1 生命周期函数 #
| 函数 | 触发时机 | 用途 |
|---|---|---|
onMount |
组件挂载后 | 初始化、数据获取 |
onCleanup |
组件卸载/Effect 清理 | 资源清理 |
createEffect |
依赖变化时 | 响应式副作用 |
7.2 使用场景 #
| 场景 | 推荐使用 |
|---|---|
| DOM 操作 | onMount |
| 数据获取 | onMount 或 createResource |
| 事件监听 | onMount + onCleanup |
| 定时器 | onMount + onCleanup |
| 响应式更新 | createEffect |
7.3 最佳实践 #
- 资源清理:始终在 onMount 中注册 onCleanup
- 组织代码:按状态、生命周期、Effects、渲染顺序组织
- 避免泄漏:清理所有订阅、定时器、事件监听
- 使用 Hooks:封装可复用的生命周期逻辑
最后更新:2026-03-28