Effect副作用 #
一、createEffect 基础 #
1.1 什么是 Effect #
Effect 是 Solid 的副作用原语,用于在响应式状态变化时执行副作用操作。
jsx
import { createSignal, createEffect } from 'solid-js';
const [count, setCount] = createSignal(0);
// 创建副作用
createEffect(() => {
console.log('Count changed:', count());
});
setCount(1); // 自动执行 Effect
1.2 Effect 结构 #
text
┌─────────────────────────────────────────────────────────────┐
│ Effect 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 首次执行 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ createEffect(() => { │ │
│ │ console.log(count()); // 读取 Signal │ │
│ │ }) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 2. 收集依赖 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ count() 被读取 → 添加到依赖列表 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 3. 依赖变化时重新执行 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ setCount(1) → count 变化 → 重新执行 Effect │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
二、基本用法 #
2.1 简单副作用 #
jsx
function Counter() {
const [count, setCount] = createSignal(0);
createEffect(() => {
document.title = `Count: ${count()}`;
});
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
2.2 多依赖 #
jsx
function UserProfile() {
const [firstName, setFirstName] = createSignal('John');
const [lastName, setLastName] = createSignal('Doe');
createEffect(() => {
console.log(`User: ${firstName()} ${lastName()}`);
});
// firstName 或 lastName 变化都会触发 Effect
}
2.3 条件依赖 #
jsx
function ConditionalEffect() {
const [show, setShow] = createSignal(true);
const [count, setCount] = createSignal(0);
createEffect(() => {
if (show()) {
// 只有 show() 为 true 时才追踪 count
console.log('Count:', count());
} else {
console.log('Hidden');
}
});
return (
<div>
<button onClick={() => setShow(s => !s)}>Toggle</button>
<button onClick={() => setCount(c => c + 1)}>Count++</button>
</div>
);
}
三、清理机制 #
3.1 onCleanup #
使用 onCleanup 在 Effect 清理时执行操作:
jsx
import { createSignal, createEffect, onCleanup } from 'solid-js';
function Timer() {
const [seconds, setSeconds] = createSignal(0);
createEffect(() => {
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 EventListener() {
const [position, setPosition] = createSignal({ x: 0, y: 0 });
createEffect(() => {
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 WebSocket 示例 #
jsx
function WebSocketChat() {
const [messages, setMessages] = createSignal([]);
const [connected, setConnected] = createSignal(false);
createEffect(() => {
const ws = new WebSocket('wss://example.com/chat');
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>
);
}
四、Effect 配置 #
4.1 延迟执行 #
jsx
import { createSignal, createEffect } from 'solid-js';
const [count, setCount] = createSignal(0);
// 默认:立即执行
createEffect(() => {
console.log('Immediate:', count());
});
// 延迟执行:首次不执行,依赖变化后才执行
createEffect(() => {
console.log('Deferred:', count());
}, { defer: true });
4.2 使用 on 函数 #
jsx
import { createSignal, createEffect, on } from 'solid-js';
const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);
// 显式声明依赖
createEffect(on(a, (value) => {
console.log('a changed:', value);
}));
// 多依赖
createEffect(on([a, b], ([aVal, bVal], prevValues) => {
console.log('a or b changed:', aVal, bVal);
}));
// 带选项
createEffect(on(a, (value) => {
console.log('a changed:', value);
}, { defer: true }));
4.3 on 函数选项 #
jsx
// defer: 延迟首次执行
createEffect(on(count, (value) => {
console.log('Count:', value);
}, { defer: true }));
// 完整示例
createEffect(on(
[firstName, lastName],
([first, last], prev) => {
console.log(`Name: ${first} ${last}`);
console.log('Previous:', prev);
},
{ defer: true }
));
五、高级用法 #
5.1 嵌套 Effect #
jsx
function NestedEffects() {
const [user, setUser] = createSignal(null);
const [posts, setPosts] = createSignal([]);
createEffect(() => {
const u = user();
if (u) {
// 内层 Effect 在 user 存在时才创建
createEffect(() => {
console.log('User posts:', posts());
});
}
});
}
5.2 动态 Effect #
jsx
function DynamicEffects() {
const [items, setItems] = createSignal([]);
createEffect(() => {
items().forEach((item, index) => {
// 为每个 item 创建独立的 Effect
createEffect(() => {
console.log(`Item ${index}:`, item.name);
});
});
});
}
5.3 批量更新中的 Effect #
jsx
import { createSignal, createEffect, batch } from 'solid-js';
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);
createEffect(() => {
console.log('Effect:', a(), b());
});
// 批量更新:Effect 只执行一次
batch(() => {
setA(10);
setB(20);
});
六、实际应用 #
6.1 本地存储同步 #
jsx
function PersistentState() {
const [todos, setTodos] = createSignal(
JSON.parse(localStorage.getItem('todos') || '[]')
);
createEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos()));
});
const addTodo = (text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
};
return (
<div>
<button onClick={() => addTodo('New task')}>Add</button>
<ul>
<For each={todos()}>
{(todo) => <li>{todo.text}</li>}
</For>
</ul>
</div>
);
}
6.2 API 请求 #
jsx
function UserProfile({ userId }) {
const [user, setUser] = createSignal(null);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(null);
createEffect(() => {
const id = userId();
if (!id) return;
setLoading(true);
setError(null);
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
});
return (
<div>
<Show when={loading()}>
<p>Loading...</p>
</Show>
<Show when={error()}>
<p>Error: {error()}</p>
</Show>
<Show when={user()}>
{(u) => (
<div>
<h2>{u().name}</h2>
<p>{u().email}</p>
</div>
)}
</Show>
</div>
);
}
6.3 文档标题管理 #
jsx
function useDocumentTitle(title) {
createEffect(() => {
const previousTitle = document.title;
document.title = title();
onCleanup(() => {
document.title = previousTitle;
});
});
}
function Page() {
const [pageTitle, setPageTitle] = createSignal('Home');
useDocumentTitle(pageTitle);
return (
<div>
<input
value={pageTitle()}
onInput={(e) => setPageTitle(e.target.value)}
/>
</div>
);
}
6.4 订阅管理 #
jsx
function useSubscription(channel) {
const [messages, setMessages] = createSignal([]);
createEffect(() => {
const ch = channel();
if (!ch) return;
const handler = (message) => {
setMessages(prev => [...prev, message]);
};
ch.subscribe(handler);
onCleanup(() => {
ch.unsubscribe(handler);
});
});
return messages;
}
七、Effect 注意事项 #
7.1 避免无限循环 #
jsx
// 错误:无限循环
createEffect(() => {
setCount(count() + 1); // Effect 中修改依赖的 Signal
});
// 正确:使用事件触发
function Counter() {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log('Count:', count());
});
return (
<button onClick={() => setCount(c => c + 1)}>+</button>
);
}
7.2 异步操作 #
jsx
// 注意:异步中读取 Signal 不会追踪依赖
createEffect(() => {
setTimeout(() => {
console.log(count()); // 不会追踪 count
}, 1000);
});
// 正确:同步读取后传递
createEffect(() => {
const value = count(); // 同步读取,追踪依赖
setTimeout(() => {
console.log(value);
}, 1000);
});
7.3 条件渲染 #
jsx
function ConditionalEffect() {
const [show, setShow] = createSignal(true);
const [count, setCount] = createSignal(0);
// 正确:在条件内读取 Signal
createEffect(() => {
if (show()) {
console.log(count()); // 只在 show() 为 true 时追踪
}
});
return (
<div>
<button onClick={() => setShow(s => !s)}>Toggle</button>
<Show when={show()}>
<button onClick={() => setCount(c => c + 1)}>Count++</button>
</Show>
</div>
);
}
八、总结 #
8.1 Effect 核心 API #
| API | 说明 |
|---|---|
createEffect(fn) |
创建 Effect |
createEffect(fn, options) |
带选项 |
onCleanup(fn) |
注册清理函数 |
on(deps, fn) |
显式声明依赖 |
8.2 Effect 选项 #
| 选项 | 说明 |
|---|---|
defer |
延迟首次执行 |
8.3 最佳实践 #
- 清理资源:使用
onCleanup清理定时器、事件监听器等 - 避免循环:不要在 Effect 中修改自己依赖的 Signal
- 显式依赖:使用
on函数明确依赖 - 异步处理:在同步上下文中读取 Signal
- 条件依赖:理解动态依赖追踪行为
8.4 常见用途 #
- 数据同步(localStorage、服务器)
- 事件监听
- 定时器管理
- WebSocket 连接
- 文档标题更新
- 日志记录
最后更新:2026-03-28