组件基础 #

一、组件概述 #

1.1 什么是组件 #

组件是 Preact 应用的基本构建块,将 UI 拆分为独立、可复用的部分。

text
应用
├── Header 组件
├── Main 组件
│   ├── Sidebar 组件
│   └── Content 组件
└── Footer 组件

1.2 组件类型 #

类型 说明 推荐
函数组件 使用函数定义 ✅ 推荐
类组件 使用类定义 兼容场景

二、函数组件 #

2.1 基本定义 #

jsx
// 函数声明
function Welcome() {
  return <h1>Hello, Preact!</h1>;
}

// 箭头函数
const Welcome = () => <h1>Hello, Preact!</h1>;

// 导出组件
export function Welcome() {
  return <h1>Hello, Preact!</h1>;
}

2.2 接收 Props #

jsx
function Greeting({ name, age }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
    </div>
  );
}

// 使用
<Greeting name="Alice" age={25} />

2.3 默认 Props #

jsx
function Button({ text = 'Click me', type = 'button' }) {
  return (
    <button type={type}>
      {text}
    </button>
  );
}

// 使用
<Button />                    // text="Click me"
<Button text="Submit" />      // text="Submit"

2.4 Props 解构 #

jsx
function UserCard({ 
  user: { name, email, avatar },
  showEmail = true 
}) {
  return (
    <div class="card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      {showEmail && <p>{email}</p>}
    </div>
  );
}

2.5 children 属性 #

jsx
function Card({ children, title }) {
  return (
    <div class="card">
      {title && <h2>{title}</h2>}
      <div class="card-body">
        {children}
      </div>
    </div>
  );
}

// 使用
<Card title="Welcome">
  <p>This is the card content.</p>
  <button>Click me</button>
</Card>

2.6 完整示例 #

jsx
import { useState } from 'preact/hooks';

function Counter({ initialCount = 0, step = 1 }) {
  const [count, setCount] = useState(initialCount);

  return (
    <div class="counter">
      <h2>Counter: {count}</h2>
      <button onClick={() => setCount(c => c - step)}>
        -{step}
      </button>
      <button onClick={() => setCount(c => c + step)}>
        +{step}
      </button>
    </div>
  );
}

三、类组件 #

3.1 基本定义 #

jsx
import { Component } from 'preact';

class Welcome extends Component {
  render() {
    return <h1>Hello, Preact!</h1>;
  }
}

3.2 使用 Props #

jsx
import { Component } from 'preact';

class Greeting extends Component {
  render() {
    const { name } = this.props;
    return <h1>Hello, {name}!</h1>;
  }
}

3.3 使用 State #

jsx
import { Component } from 'preact';

class Counter extends Component {
  state = { count: 0 };

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  render() {
    return (
      <div>
        <h2>Count: {this.state.count}</h2>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

3.4 函数式 setState #

jsx
class Counter extends Component {
  state = { count: 0 };

  increment = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

3.5 类组件 vs 函数组件 #

jsx
// 类组件
class Toggle extends Component {
  state = { on: false };

  toggle = () => {
    this.setState(prev => ({ on: !prev.on }));
  };

  render() {
    return (
      <button onClick={this.toggle}>
        {this.state.on ? 'ON' : 'OFF'}
      </button>
    );
  }
}

// 函数组件(推荐)
function Toggle() {
  const [on, setOn] = useState(false);

  return (
    <button onClick={() => setOn(!on)}>
      {on ? 'ON' : 'OFF'}
    </button>
  );
}

四、组件组合 #

4.1 组件嵌套 #

jsx
function App() {
  return (
    <div class="app">
      <Header />
      <Main>
        <Sidebar />
        <Content />
      </Main>
      <Footer />
    </div>
  );
}

function Header() {
  return <header>Header</header>;
}

function Main({ children }) {
  return <main>{children}</main>;
}

function Sidebar() {
  return <aside>Sidebar</aside>;
}

function Content() {
  return <section>Content</section>;
}

function Footer() {
  return <footer>Footer</footer>;
}

4.2 组件作为 Props #

jsx
function Layout({ header, sidebar, content }) {
  return (
    <div class="layout">
      <header>{header}</header>
      <div class="body">
        <aside>{sidebar}</aside>
        <main>{content}</main>
      </div>
    </div>
  );
}

// 使用
<Layout
  header={<h1>My App</h1>}
  sidebar={<nav>Navigation</nav>}
  content={<article>Article content</article>}
/>

4.3 Render Props 模式 #

jsx
function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return render({ data, loading });
}

// 使用
<DataFetcher
  url="/api/users"
  render={({ data, loading }) => (
    loading 
      ? <p>Loading...</p>
      : <UserList users={data} />
  )}
/>

五、组件类型 #

5.1 展示组件 #

jsx
// 只负责展示,不包含业务逻辑
function UserCard({ user }) {
  return (
    <div class="user-card">
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

5.2 容器组件 #

jsx
// 负责数据获取和状态管理
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUsers().then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);

  if (loading) return <Loading />;

  return <UserList users={users} />;
}

5.3 高阶组件 #

jsx
function withLoading(WrappedComponent) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) {
      return <Loading />;
    }
    return <WrappedComponent {...props} />;
  };
}

// 使用
const UserListWithLoading = withLoading(UserList);

<UserListWithLoading isLoading={loading} users={users} />

六、组件通信 #

6.1 父传子(Props) #

jsx
function Parent() {
  const [message, setMessage] = useState('Hello');

  return <Child message={message} />;
}

function Child({ message }) {
  return <p>{message}</p>;
}

6.2 子传父(回调函数) #

jsx
function Parent() {
  const [data, setData] = useState('');

  const handleData = (newData) => {
    setData(newData);
  };

  return (
    <div>
      <p>Received: {data}</p>
      <Child onSend={handleData} />
    </div>
  );
}

function Child({ onSend }) {
  return (
    <button onClick={() => onSend('Hello from child')}>
      Send Data
    </button>
  );
}

6.3 兄弟组件通信 #

jsx
function Parent() {
  const [sharedData, setSharedData] = useState('');

  return (
    <div>
      <SiblingA onData={setSharedData} />
      <SiblingB data={sharedData} />
    </div>
  );
}

function SiblingA({ onData }) {
  return (
    <button onClick={() => onData('Data from A')}>
      Send to B
    </button>
  );
}

function SiblingB({ data }) {
  return <p>Received: {data}</p>;
}

七、组件命名规范 #

7.1 命名约定 #

jsx
// 组件名:PascalCase
function UserProfile() {}
function ShoppingCart() {}

// 文件名:PascalCase 或 kebab-case
// UserProfile.jsx 或 user-profile.jsx

// Props:camelCase
<UserProfile userName="Alice" isActive={true} />

// 事件处理函数:handle 前缀
const handleClick = () => {};
const handleSubmit = () => {};

7.2 组件组织 #

text
src/
├── components/
│   ├── common/           通用组件
│   │   ├── Button.jsx
│   │   ├── Input.jsx
│   │   └── Modal.jsx
│   ├── layout/           布局组件
│   │   ├── Header.jsx
│   │   ├── Footer.jsx
│   │   └── Sidebar.jsx
│   └── features/         功能组件
│       ├── UserCard.jsx
│       └── ProductList.jsx

八、组件最佳实践 #

8.1 单一职责 #

jsx
// 好的做法:每个组件只做一件事
function UserName({ name }) {
  return <span class="user-name">{name}</span>;
}

function UserAvatar({ url, alt }) {
  return <img class="user-avatar" src={url} alt={alt} />;
}

function UserCard({ user }) {
  return (
    <div class="user-card">
      <UserAvatar url={user.avatar} alt={user.name} />
      <UserName name={user.name} />
    </div>
  );
}

8.2 保持组件简单 #

jsx
// 避免:过于复杂的组件
function BadComponent({ data, config, user, theme }) {
  // 100+ 行代码...
}

// 推荐:拆分为小组件
function GoodComponent({ data }) {
  return (
    <div>
      <Header />
      <Content data={data} />
      <Footer />
    </div>
  );
}

8.3 提取可复用逻辑 #

jsx
// 自定义 Hook
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 使用
function ResponsiveComponent() {
  const { width } = useWindowSize();

  return (
    <div>
      {width < 768 ? <MobileView /> : <DesktopView />}
    </div>
  );
}

九、总结 #

要点 说明
函数组件 推荐使用,更简洁
Props 只读,父传子
children 组件内容插槽
组合 组件嵌套和复用
通信 Props 向下,回调向上

函数组件优势:

  • 代码更简洁
  • 没有 this 绑定问题
  • 更容易测试
  • 更好的性能
最后更新:2026-03-28