函数组件(最常见)

function Button({ children, onClick }: Props) {
  return <button onClick={onClick}>{children}</button>;
}

2025 年默认就用这个。Class 组件已经不需要学新的——老项目维护时认识就行。

Class 组件(认识即可)

class Counter extends React.Component {
  state = { n: 0 };
  render() {
    return <button onClick={() => this.setState({ n: this.state.n + 1 })}>
      {this.state.n}
    </button>;
  }
}

新代码不要写。所有能力(state / lifecycle / context)函数组件 + Hooks 都能做,而且更简洁、可组合性强。

Server Component vs Client Component(Next.js App Router)

Next.js 13+ 引入的新区别。和 SSR 不是一回事,更深一层。

Server Component Client Component
在哪运行 只在服务器 服务器(首屏)+ 浏览器(hydration 后)
能用 hooks 不能 useState / useEffect 全部能
能用浏览器 API 不能
能直接 await (async function) 不能
Bundle 大小 不进客户端 bundle
默认 App Router 默认 'use client' 才是
// 默认 Server Component
async function PostList() {
  const posts = await db.post.findMany();   // 直接查库
  return posts.map(p => <Post key={p.id} {...p} />);
}

// 用了 hook / 事件 → 必须 Client
'use client';
function LikeButton() {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>;
}

默认 Server,需要交互再 Client。这是新最佳实践。

受控 vs 非受控(表单组件)

// 受控:value 来自 state,每次输入都 setState
<input value={text} onChange={e => setText(e.target.value)} />

// 非受控:DOM 自己存值,用 ref 取
<input ref={inputRef} defaultValue="" />
// inputRef.current.value

详见 第 14 篇

组合模式 1:children

<Card>
  <h2>标题</h2>
  <p>内容</p>
</Card>

function Card({ children }) {
  return <div className="card">{children}</div>;
}

最简单、最常用。先想 children 能不能搞定,再想其他模式。

组合模式 2:插槽(多个 children)

function Page({ header, sidebar, main }) {
  return (
    <div>
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{main}</main>
    </div>
  );
}

<Page
  header={<Nav />}
  sidebar={<Filters />}
  main={<List />}
/>

组合模式 3:render prop

<MouseTracker>
  {(pos) => <div>当前在 {pos.x},{pos.y}</div>}
</MouseTracker>

老模式,现在大多用 Hook 替代const pos = useMouse();。但库还有用,需要认识。

组合模式 4:HOC(高阶组件)

const withAuth = (Component) => (props) =>
  isLogin ? <Component {...props} /> : <Login />;

const ProtectedPage = withAuth(Page);

老模式,已经被 Hooks 取代 95%。新代码用 useAuth() 在组件里判断就行。HOC 在路由、错误边界这种"包裹"语义场景还有少量用武之地。

Compound Component(复合组件)

<Tabs>
  <Tabs.List>
    <Tabs.Tab>A</Tabs.Tab>
    <Tabs.Tab>B</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel>...</Tabs.Panel>
</Tabs>

通过 Context 让子组件共享父状态。Radix UI / Headless UI 大量用这种模式。

选型直觉

  • 渲染数据 → 函数组件
  • 没交互的页面 / 列表 → Server Component
  • 有交互 / 浏览器 API → Client Component
  • 复用逻辑 → 自定义 Hook(不是 HOC
  • 复用 UI 结构 → children / 插槽
  • 一组语义紧密的组件 → 复合组件 + Context

→ 下一篇 useState 深入