组件基础 #

一、组件概述 #

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 组件设计原则 #

  1. 单一职责:每个组件只做一件事
  2. 可复用:组件应该可复用
  3. 可组合:小组件组合成大组件
  4. 可测试:组件易于测试
  5. 清晰接口:Props 定义清晰

8.3 最佳实践 #

  • 组件名首字母大写
  • 保持组件纯净
  • 合理拆分组件
  • 使用 TypeScript 类型
  • 编写组件文档
最后更新:2026-03-28