第一个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 #

  1. 安装浏览器扩展
  2. 打开开发者工具
  3. 切换到 Solid 标签页
  4. 查看组件树和 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 应用的关键步骤:

  1. 创建 Signal:使用 createSignal 管理状态
  2. 定义组件:函数返回 JSX
  3. 渲染应用:使用 render 函数
  4. 处理事件:绑定事件处理函数
  5. 使用控制流ShowFor 等组件

核心要点:

  • Signal 是函数,读取时需要调用
  • 更新是细粒度的,只更新变化的部分
  • 使用控制流组件而非 JavaScript 语法
  • 组件只渲染一次,响应式系统处理更新
最后更新:2026-03-28