组件基础 #
一、组件概述 #
1.1 什么是组件 #
组件是 Solid 应用的基本构建块,用于封装可复用的 UI 和逻辑。
jsx
// 一个简单的组件
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用组件
<Greeting name="World" />
1.2 组件特点 #
text
┌─────────────────────────────────────────────────────────────┐
│ Solid 组件特点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 函数组件 │
│ └── 组件是返回 JSX 的函数 │
│ │
│ 2. 单次渲染 │
│ └── 组件函数只执行一次 │
│ │
│ 3. 响应式更新 │
│ └── 通过 Signal 和 Effect 实现更新 │
│ │
│ 4. 无生命周期钩子 │
│ └── 使用 createEffect 替代 │
│ │
│ 5. Props 只读 │
│ └── 不应修改传入的 props │
│ │
└─────────────────────────────────────────────────────────────┘
二、定义组件 #
2.1 函数声明 #
jsx
function Button(props) {
return (
<button onClick={props.onClick}>
{props.label}
</button>
);
}
2.2 箭头函数 #
jsx
const Card = (props) => (
<div class="card">
<h2>{props.title}</h2>
<p>{props.content}</p>
</div>
);
2.3 带状态的组件 #
jsx
function Counter() {
const [count, setCount] = createSignal(0);
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
2.4 组件命名规范 #
jsx
// 正确:组件名首字母大写
function UserProfile() { }
const NavBar = () => { };
// 错误:组件名首字母小写
function userProfile() { } // 会被当作 HTML 标签
const navBar = () => { };
三、组件组合 #
3.1 children 属性 #
jsx
function Card(props) {
return (
<div class="card">
<div class="card-body">
{props.children}
</div>
</div>
);
}
// 使用
<Card>
<h2>Title</h2>
<p>Content</p>
</Card>
3.2 多插槽 #
jsx
function Layout(props) {
return (
<div class="layout">
<header class="header">
{props.header}
</header>
<main class="main">
{props.children}
</main>
<footer class="footer">
{props.footer}
</footer>
</div>
);
}
// 使用
<Layout
header={<nav>Navigation</nav>}
footer={<p>Copyright</p>}
>
<article>Content</article>
</Layout>
3.3 渲染函数 #
jsx
function List(props) {
return (
<ul>
{props.items.map((item, index) =>
props.renderItem(item, index)
)}
</ul>
);
}
// 使用
<List
items={users}
renderItem={(user, index) => (
<li key={user.id}>
{index}: {user.name}
</li>
)}
/>
四、组件渲染 #
4.1 渲染机制 #
text
┌─────────────────────────────────────────────────────────────┐
│ Solid 渲染流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 组件初始化 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ function Component() { │ │
│ │ // 创建 Signal │ │
│ │ const [count, setCount] = createSignal(0); │ │
│ │ │ │
│ │ // 设置 Effect │ │
│ │ createEffect(() => { ... }); │ │
│ │ │ │
│ │ // 返回 JSX │ │
│ │ return <div>{count()}</div>; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 2. JSX 编译 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ JSX 转换为 DOM 创建代码 │ │
│ │ 创建响应式绑定 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 3. 响应式更新 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Signal 变化 → 精确更新 DOM │ │
│ │ 组件函数不再执行 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 与 React 的区别 #
jsx
// React:状态变化时组件重新执行
function ReactCounter() {
const [count, setCount] = useState(0);
console.log('Component rendered'); // 每次更新都执行
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Solid:组件只执行一次
function SolidCounter() {
const [count, setCount] = createSignal(0);
console.log('Component initialized'); // 只执行一次
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}
4.3 渲染入口 #
jsx
import { render } from 'solid-js/web';
function App() {
return <h1>Hello Solid!</h1>;
}
// 渲染到 DOM
render(() => <App />, document.getElementById('root'));
五、组件拆分 #
5.1 按职责拆分 #
jsx
// 拆分前:一个大组件
function UserDashboard() {
const [user, setUser] = createSignal(null);
const [posts, setPosts] = createSignal([]);
const [stats, setStats] = createSignal({});
// 大量逻辑...
return (
<div>
{/* 大量 JSX */}
</div>
);
}
// 拆分后:多个小组件
function UserDashboard() {
const [user, setUser] = createSignal(null);
return (
<div>
<UserHeader user={user()} />
<UserStats userId={user()?.id} />
<UserPosts userId={user()?.id} />
</div>
);
}
function UserHeader(props) {
return (
<header>
<h1>{props.user?.name}</h1>
</header>
);
}
function UserStats(props) {
const [stats] = createResource(() => props.userId, fetchStats);
return <div>{/* stats display */}</div>;
}
function UserPosts(props) {
const [posts] = createResource(() => props.userId, fetchPosts);
return <div>{/* posts list */}</div>;
}
5.2 提取可复用组件 #
jsx
// 可复用的按钮组件
function Button(props) {
return (
<button
class={`btn btn-${props.variant || 'primary'}`}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
);
}
// 使用
<Button variant="danger" onClick={handleDelete}>
Delete
</Button>
<Button variant="success" onClick={handleSave}>
Save
</Button>
5.3 组件层级 #
text
App
├── Header
│ ├── Logo
│ └── Navigation
│ └── NavItem
├── Main
│ ├── Sidebar
│ │ └── Menu
│ └── Content
│ ├── Article
│ │ └── Comment
│ └── Pagination
└── Footer
└── Links
六、组件模式 #
6.1 容器/展示模式 #
jsx
// 容器组件:处理逻辑
function UserListContainer() {
const [users] = createResource(fetchUsers);
const [selectedId, setSelectedId] = createSignal(null);
return (
<UserList
users={users()}
selectedId={selectedId()}
onSelect={setSelectedId}
/>
);
}
// 展示组件:只负责渲染
function UserList(props) {
return (
<ul>
<For each={props.users}>
{(user) => (
<li
class={props.selectedId === user.id ? 'selected' : ''}
onClick={() => props.onSelect(user.id)}
>
{user.name}
</li>
)}
</For>
</ul>
);
}
6.2 高阶组件模式 #
jsx
function withLoading(Component) {
return (props) => {
return (
<Show when={!props.loading} fallback={<p>Loading...</p>}>
<Component {...props} />
</Show>
);
};
}
// 使用
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading users={users()} loading={loading()} />
6.3 Render Props 模式 #
jsx
function MouseTracker(props) {
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 props.children(position());
}
// 使用
<MouseTracker>
{(pos) => (
<p>Position: {pos.x}, {pos.y}</p>
)}
</MouseTracker>
七、组件最佳实践 #
7.1 保持组件纯净 #
jsx
// 好的做法:组件不修改外部状态
function UserCard(props) {
return (
<div>
<h2>{props.user.name}</h2>
<p>{props.user.email}</p>
</div>
);
}
// 避免:组件修改 props
function BadCard(props) {
props.user.name = 'Modified'; // 不要这样做
return <div>{props.user.name}</div>;
}
7.2 合理使用 Signal #
jsx
// 好的做法:Signal 在组件内创建
function Counter() {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}
// 外部 Signal:用于跨组件共享
const [globalCount, setGlobalCount] = createRoot(() => createSignal(0));
function GlobalCounter() {
return <button onClick={() => setGlobalCount(c => c + 1)}>{globalCount()}</button>;
}
7.3 组件文档化 #
jsx
/**
* 用户卡片组件
* @param {Object} props
* @param {Object} props.user - 用户信息对象
* @param {string} props.user.name - 用户名
* @param {string} props.user.email - 用户邮箱
* @param {Function} props.onEdit - 编辑回调
*/
function UserCard(props) {
return (
<div class="user-card">
<h2>{props.user.name}</h2>
<p>{props.user.email}</p>
<button onClick={() => props.onEdit?.(props.user)}>
Edit
</button>
</div>
);
}
八、总结 #
8.1 组件核心概念 #
| 概念 | 说明 |
|---|---|
| 函数组件 | 组件是返回 JSX 的函数 |
| 单次渲染 | 组件函数只执行一次 |
| Props | 组件的输入属性 |
| children | 组件的内容插槽 |
| Signal | 组件内部状态 |
8.2 组件设计原则 #
- 单一职责:每个组件只做一件事
- 可复用:组件应该可复用
- 可组合:小组件组合成大组件
- 可测试:组件易于测试
- 清晰接口:Props 定义清晰
8.3 最佳实践 #
- 组件名首字母大写
- 保持组件纯净
- 合理拆分组件
- 使用 TypeScript 类型
- 编写组件文档
最后更新:2026-03-28