函数组件(最常见)
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 深入