第一个Solid应用 #
一、创建计数器应用 #
让我们从最简单的计数器应用开始,了解 Solid 的核心概念。
1.1 完整代码 #
jsx
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount(count() + 1);
const decrement = () => setCount(count() - 1);
const reset = () => setCount(0);
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<h1>计数器</h1>
<p style={{ fontSize: '48px', margin: '20px 0' }}>
{count()}
</p>
<div>
<button onClick={decrement}>-1</button>
<button onClick={reset} style={{ margin: '0 10px' }}>
重置
</button>
<button onClick={increment}>+1</button>
</div>
</div>
);
}
render(() => <Counter />, document.getElementById('root'));
1.2 代码解析 #
text
┌─────────────────────────────────────────────────────────────┐
│ Solid 应用结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 导入依赖 │
│ ├── createSignal: 创建响应式状态 │
│ └── render: 渲染组件到 DOM │
│ │
│ 2. 定义组件 │
│ └── Counter 函数返回 JSX │
│ │
│ 3. 创建状态 │
│ └── const [count, setCount] = createSignal(0) │
│ ├── count(): 读取值 │
│ └── setCount(): 设置值 │
│ │
│ 4. 渲染应用 │
│ └── render(() => <Counter />, rootElement) │
│ │
└─────────────────────────────────────────────────────────────┘
二、理解 Signal #
2.1 什么是 Signal #
Signal 是 Solid 的核心响应式原语,用于管理可变状态。
jsx
import { createSignal } from 'solid-js';
// 创建 Signal
const [count, setCount] = createSignal(0);
// 读取值
console.log(count()); // 0
// 设置值
setCount(5);
console.log(count()); // 5
// 基于当前值更新
setCount(prev => prev + 1);
console.log(count()); // 6
2.2 Signal vs React State #
jsx
// React
const [count, setCount] = useState(0);
// 读取:count(变量)
// 设置:setCount(5)
// Solid
const [count, setCount] = createSignal(0);
// 读取:count()(函数调用)
// 设置:setCount(5)
关键区别:
| 特性 | React State | Solid Signal |
|---|---|---|
| 读取方式 | 变量 | 函数调用 |
| 更新触发 | 组件重渲染 | 精确更新 |
| 依赖追踪 | 无 | 自动追踪 |
2.3 对象类型 Signal #
jsx
function UserForm() {
const [user, setUser] = createSignal({
name: 'Alice',
age: 25,
email: 'alice@example.com'
});
const updateName = (newName) => {
setUser(prev => ({ ...prev, name: newName }));
};
const updateAge = (newAge) => {
setUser(prev => ({ ...prev, age: newAge }));
};
return (
<div>
<p>Name: {user().name}</p>
<p>Age: {user().age}</p>
<input
value={user().name}
onInput={(e) => updateName(e.target.value)}
/>
<input
type="number"
value={user().age}
onInput={(e) => updateAge(parseInt(e.target.value))}
/>
</div>
);
}
三、构建待办事项应用 #
3.1 完整实现 #
jsx
import { createSignal, createMemo, For, Show } from 'solid-js';
import { render } from 'solid-js/web';
import './style.css';
function TodoApp() {
const [todos, setTodos] = createSignal([]);
const [input, setInput] = createSignal('');
const [filter, setFilter] = createSignal('all');
const addTodo = (e) => {
e.preventDefault();
if (input().trim()) {
setTodos(prev => [
...prev,
{ id: Date.now(), text: input(), completed: false }
]);
setInput('');
}
};
const toggleTodo = (id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
const deleteTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
const filteredTodos = createMemo(() => {
const currentFilter = filter();
const allTodos = todos();
switch (currentFilter) {
case 'active':
return allTodos.filter(t => !t.completed);
case 'completed':
return allTodos.filter(t => t.completed);
default:
return allTodos;
}
});
const remaining = createMemo(() =>
todos().filter(t => !t.completed).length
);
return (
<div class="todo-app">
<h1>待办事项</h1>
<form onSubmit={addTodo}>
<input
value={input()}
onInput={(e) => setInput(e.target.value)}
placeholder="添加新任务..."
/>
<button type="submit">添加</button>
</form>
<div class="filters">
<button
class={filter() === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
全部
</button>
<button
class={filter() === 'active' ? 'active' : ''}
onClick={() => setFilter('active')}
>
未完成
</button>
<button
class={filter() === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
已完成
</button>
</div>
<ul class="todo-list">
<For each={filteredTodos()}>
{(todo) => (
<li class={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>
删除
</button>
</li>
)}
</For>
</ul>
<Show when={todos().length > 0}>
<p class="remaining">
还有 {remaining()} 项待办事项
</p>
</Show>
</div>
);
}
render(() => <TodoApp />, document.getElementById('root'));
3.2 样式文件 #
css
/* style.css */
.todo-app {
max-width: 500px;
margin: 50px auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.todo-app h1 {
text-align: center;
color: #333;
}
.todo-app form {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.todo-app input[type="text"] {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.todo-app button {
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.todo-app button:hover {
background: #45a049;
}
.filters {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.filters button {
flex: 1;
background: #f0f0f0;
color: #333;
}
.filters button.active {
background: #4CAF50;
color: white;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #999;
}
.todo-list span {
flex: 1;
}
.todo-list button {
background: #f44336;
padding: 5px 10px;
font-size: 12px;
}
.remaining {
text-align: center;
color: #666;
margin-top: 20px;
}
四、核心概念总结 #
4.1 响应式原语 #
jsx
// Signal - 可变状态
const [value, setValue] = createSignal(initialValue);
// Memo - 派生状态
const doubled = createMemo(() => value() * 2);
// Effect - 副作用
createEffect(() => {
console.log('Value changed:', value());
});
4.2 控制流组件 #
jsx
// 条件渲染
<Show when={condition}>
<p>条件为真时显示</p>
</Show>
// 列表渲染
<For each={items()}>
{(item) => <li>{item.name}</li>}
</For>
// 条件切换
<Switch fallback={<p>默认</p>}>
<Match when={value() === 1}>选项1</Match>
<Match when={value() === 2}>选项2</Match>
</Switch>
4.3 事件处理 #
jsx
// 点击事件
<button onClick={() => setCount(c => c + 1)}>点击</button>
// 表单事件
<input
value={text()}
onInput={(e) => setText(e.target.value)}
/>
// 表单提交
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}>
五、调试技巧 #
5.1 使用 createEffect 调试 #
jsx
function DebugComponent() {
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal('Alice');
// 调试状态变化
createEffect(() => {
console.log('Count:', count());
});
createEffect(() => {
console.log('Name:', name());
});
return (
<div>
<p>Count: {count()}</p>
<p>Name: {name()}</p>
</div>
);
}
5.2 使用 Solid DevTools #
- 安装浏览器扩展
- 打开开发者工具
- 切换到 Solid 标签页
- 查看组件树和 Signal 状态
六、最佳实践 #
6.1 组件拆分 #
jsx
// 拆分前
function TodoApp() {
// 所有逻辑在一个组件中
}
// 拆分后
function TodoItem(props) {
return (
<li class={props.todo.completed ? 'completed' : ''}>
<input type="checkbox" checked={props.todo.completed} />
<span>{props.todo.text}</span>
<button onClick={() => props.onDelete(props.todo.id)}>
删除
</button>
</li>
);
}
function TodoList(props) {
return (
<ul>
<For each={props.todos}>
{(todo) => (
<TodoItem
todo={todo}
onDelete={props.onDelete}
/>
)}
</For>
</ul>
);
}
function TodoApp() {
// 只保留状态管理和协调逻辑
}
6.2 状态提升 #
jsx
// 状态提升到共同父组件
function App() {
const [user, setUser] = createSignal(null);
return (
<>
<Header user={user()} />
<Main user={user()} onLogin={setUser} />
<Footer user={user()} />
</>
);
}
七、总结 #
创建 Solid 应用的关键步骤:
- 创建 Signal:使用
createSignal管理状态 - 定义组件:函数返回 JSX
- 渲染应用:使用
render函数 - 处理事件:绑定事件处理函数
- 使用控制流:
Show、For等组件
核心要点:
- Signal 是函数,读取时需要调用
- 更新是细粒度的,只更新变化的部分
- 使用控制流组件而非 JavaScript 语法
- 组件只渲染一次,响应式系统处理更新
最后更新:2026-03-28