Solid.js Hello World — 响应式计数器
目标
展示 Solid.js 核心概念:Signal、Effect、JSX 与控制流组件。Solid 的 JSX 语法类似 React 但行为完全不同——组件只执行一次,Signal 是真正的细粒度响应式。
完整代码
import { render } from "solid-js/web";
import App from "./App";
render(() => <App />, document.getElementById("root"));
import { createSignal, createEffect, onCleanup } from "solid-js";
import { Show, For, Switch, Match } from "solid-js";
export default function App() {
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal("朋友");
const [todos, setTodos] = createSignal(["学 Solid.js", "写项目"]);
const [newTodo, setNewTodo] = createSignal("");
createEffect(() => {
console.log(`计数变了:${count()}`);
document.title = `点击 ${count()} 次`;
});
const timer = setInterval(() => {
}, 1000);
onCleanup(() => clearInterval(timer));
const addTodo = () => {
if (newTodo().trim()) {
setTodos([...todos(), newTodo()]);
setNewTodo("");
}
};
return (
<main style={mainStyle}>
<h1>⚛️ Solid.js Demo</h1>
<p style={{ color: "#666" }}>
Solid 的组件只渲染一次,但 Signal 保持细粒度更新
</p>
{}
<input
style={inputStyle}
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
placeholder="你的名字..."
/>
<p>
你好,<strong>{name()}</strong>!
</p>
{}
<h2 style={{ fontSize: "3rem", textAlign: "center" }}>{count()}</h2>
<div style={{ textAlign: "center", margin: "16px 0" }}>
<button style={btnStyle} onClick={() => setCount((c) => c + 1)}>
+ 1
</button>
<button style={btnStyle} onClick={() => setCount((c) => c - 1)}>
- 1
</button>
<button style={{ ...btnStyle, background: "#64748b" }} onClick={() => setCount(0)}>
重置
</button>
</div>
{}
<Show when={count() >= 10}>
<div style={{ padding: 12, background: "#fef3c7", borderRadius: 8 }}>
🎉 达到 {count()} 次!
</div>
</Show>
{}
<h3>📋 待办事项</h3>
<ul>
<For each={todos()}>
{(todo, index) => (
<li>
{index() + 1}. {todo}
<button
style={{ marginLeft: 8, color: "red", border: "none", cursor: "pointer" }}
onClick={() => setTodos(todos().filter((_, i) => i !== index()))}
>
✕
</button>
</li>
)}
</For>
</ul>
<div style={{ display: "flex", gap: 8 }}>
<input
style={{ flex: 1, ...inputStyle }}
value={newTodo()}
onInput={(e) => setNewTodo(e.currentTarget.value)}
placeholder="新待办..."
onKeyDown={(e) => e.key === "Enter" && addTodo()}
/>
<button style={btnStyle} onClick={addTodo}>
添加
</button>
</div>
{}
<div style={{ marginTop: 16, padding: 12, background: "#f0f0ff", borderRadius: 8 }}>
<Switch>
<Match when={count() === 0}>开始点击吧!</Match>
<Match when={count() < 5}>继续加油~</Match>
<Match when={count() < 10}>快突破 10 了!</Match>
<Match when={true}>🔥 你已经是大佬了</Match>
</Switch>
</div>
</main>
);
}
const mainStyle = {
"max-width": "540px",
margin: "40px auto",
padding: "0 16px",
"font-family": "-apple-system, 'Microsoft YaHei', sans-serif",
};
const inputStyle = {
width: "100%",
padding: "10px",
border: "2px solid #e2e8f0",
"border-radius": "8px",
"font-size": "1rem",
"box-sizing": "border-box",
};
const btnStyle = {
padding: "10px 24px",
border: "none",
"border-radius": "8px",
"font-size": "1rem",
cursor: "pointer",
background: "#7c4dff",
color: "white",
};
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Solid.js App</title>
</head>
<body>
<div id="root"></div>
<script src="./src/index.jsx" type="module"></script>
</body>
</html>
运行步骤
npm install
npm run dev
预期输出
- 计数器实时更新,不依赖虚拟 DOM diff
- 输入框双向绑定 name signal
- 待办列表可增删
- Switch 组件根据计数显示不同消息
- 页面标题随计数变化(createEffect)
Solid.js 响应式图书收藏
目标
用 Solid.js 实现图书收藏列表,展示 createSignal 响应式状态、createEffect 副作用和 For 控制流。
完整代码
src/index.jsx
import { render } from 'solid-js/web';
import App from './App';
render(() => <App />, document.getElementById('root'));
src/App.jsx
import { createSignal, createEffect, For, Show } from 'solid-js';
import styles from './App.module.css';
const INITIAL_BOOKS = [
{ id: 1, title: '深入理解计算机系统', author: 'Randal E. Bryant', year: 2016, read: true },
{ id: 2, title: '代码整洁之道', author: 'Robert C. Martin', year: 2010, read: false },
{ id: 3, title: '设计模式', author: 'GoF', year: 1994, read: true },
];
export default function App() {
const [books, setBooks] = createSignal(INITIAL_BOOKS);
const [newBook, setNewBook] = createSignal({ title: '', author: '', year: '' });
const [search, setSearch] = createSignal('');
const filteredBooks = () => {
const term = search().toLowerCase();
if (!term) return books();
return books().filter(b =>
b.title.toLowerCase().includes(term) ||
b.author.toLowerCase().includes(term)
);
};
createEffect(() => {
const readCount = books().filter(b => b.read).length;
console.log(`已读: ${readCount}/${books().length}`);
});
const addBook = (e) => {
e.preventDefault();
const b = newBook();
if (!b.title || !b.author) return;
setBooks(prev => [...prev, { ...b, id: Date.now(), read: false, year: Number(b.year) || 2024 }]);
setNewBook({ title: '', author: '', year: '' });
};
const toggleRead = (id) => {
setBooks(prev => prev.map(b => b.id === id ? { ...b, read: !b.read } : b));
};
const removeBook = (id) => {
setBooks(prev => prev.filter(b => b.id !== id));
};
return (
<div class={styles.app}>
<h1>📚 图书收藏</h1>
<input
type="text"
placeholder="搜索书名或作者..."
value={search()}
onInput={(e) => setSearch(e.target.value)}
class={styles.search}
/>
{}
<form onSubmit={addBook} class={styles.form}>
<input
placeholder="书名" value={newBook().title}
onInput={(e) => setNewBook(p => ({ ...p, title: e.target.value }))}
/>
<input
placeholder="作者" value={newBook().author}
onInput={(e) => setNewBook(p => ({ ...p, author: e.target.value }))}
/>
<input
placeholder="年份" type="number" value={newBook().year}
onInput={(e) => setNewBook(p => ({ ...p, year: e.target.value }))}
/>
<button type="submit">➕ 添加</button>
</form>
{}
<div class={styles.list}>
<Show when={filteredBooks().length > 0} fallback={<p style="color:#999">没有匹配的图书</p>}>
<For each={filteredBooks()}>
{(book) => (
<div class={`${styles.card} ${book.read ? styles.read : ''}`}>
<div class={styles.info}>
<strong>{book.title}</strong>
<small>{book.author} · {book.year}</small>
</div>
<div class={styles.actions}>
<button onClick={() => toggleRead(book.id)}>
{book.read ? '✅ 已读' : '📖 标记已读'}
</button>
<button onClick={() => removeBook(book.id)} class={styles.del}>🗑️</button>
</div>
</div>
)}
</For>
</Show>
</div>
<p class={styles.stats}>
共 {books().length} 本 · 已读 {books().filter(b => b.read).length} 本
</p>
</div>
);
}
运行步骤
npx degit solidjs/templates/js solid-books
cd solid-books
npm install
npm run dev
预期输出
- 展示 3 本预置图书,2 本标记已读(绿色)
- 搜索框实时过滤图书
- 添加新书后列表自动响应式更新
- 控制台输出已读统计