一句话定义
组件是一个返回 JSX 的函数:首字母大写、接收 props、可复用;无数组件嵌套起来构成整棵组件树,React 只渲染这棵树。
为什么重要
组件是 React 的唯一组织单元。「怎么拆组件、怎么组合」决定了一个项目三个月后是「加需求顺手」还是「改一行崩三处」。组件设计能力 = 前端架构能力的一半。
前置知识
- kp-002 JSX 语法。
- 函数的参数与返回值。
核心概念
- 函数组件:
function Card({ title, children }) { return <div>...</div> }。 - 组件树(Component Tree):组件 A 渲染组件 B,B 渲染 C……形成树;渲染自顶向下进行。
- 纯函数纪律:同样输入必须同样输出;不得在渲染期间改外部变量、发请求、操作 DOM。
- 嵌套 vs 组合:父组件「import 并渲染」子组件;不是字符串模板嵌套,而是函数调用链。
原理 / 机制
jsx// ✅ 纯函数:输入相同 → 输出相同,无副作用
function Avatar({ src, size = 40 }) {
return <img src={src} width={size} height={size} alt="" />;
}
// ❌ 非纯:渲染期间修改外部变量
let renderCount = 0;
function Bad() {
renderCount++; // 渲染期间写外部状态 → StrictMode 下会双倍计数
return <span>{renderCount}</span>;
}
为什么强调纯函数?因为 React 可能任意次数调用你的组件函数(缓存、并发渲染、StrictMode 双调用),只有纯函数才保证「多调几次」结果一致。StrictMode 开发模式会故意双调用组件与 effect 来暴露不纯。
一棵典型树的形状:
textApp
├── Header
│ └── SearchBox (state: keyword)
└── Main
├── Sidebar (props: categories)
└── ProductList (state: sort, page)
└── ProductCard × N (props: product)
拆分信号(官方 Thinking in React 的分层原则):
- 视觉上明显独立的一块(按钮、卡片、头部);
- 同一份 UI 在多处复用;
- 一段 JSX + 它专属的状态 + 它专属的逻辑强耦合,希望「内聚在一个函数里」(后面会升级为自定义 Hook,kp-015)。
直观类比
组件树像公司组织架构:App 是 CEO,各组件是部门。信息自上而下传递(props 像下达的目标),反馈自下而上汇报(回调函数)。跨部门直接喊话会乱套——所以默认只允许上下级通信,跨级共享要走「公示栏」(Context,kp-012)。
实例 / 案例
三层组件的传递链:
jsxfunction App() {
const products = [...]; // 假设来自 API
return <ProductList items={products} />;
}
function ProductList({ items }) {
return (
<ul>
{items.map(p => <ProductCard key={p.id} product={p} />)}
</ul>
);
}
function ProductCard({ product }) {
return (
<li>
<Avatar src={product.image} size={56} />
<strong>{product.name}</strong>
<em>¥{product.price}</em>
</li>
);
}
注意 App 与 Avatar 隔了两层却不需要互相认识——每层只认识自己的直接子组件。这种「局部认识」让任何一层都能被单独替换。
常见误区
- 组件定义嵌套:在组件函数体里
function Inner() {...}然后渲染它——每次外层渲染 Inner 都是新函数引用,子组件状态全部重置(见 kp-019 的「元素类型变化」)。 - 渲染期间做副作用:发请求、改 DOM、订阅事件都应放进 effect(kp-010)或事件处理器。
- 拆得太碎:一个组件只包一个
<span>且只被用一次,层数反而拖慢理解。「三行以内的私有片段不值得拆」是好起点。 - 以为组件名可以小写开头:
<card>会被当成 HTML 标签渲染为未知元素,什么都不显示。
自测题
- 为什么 React 要求组件是纯函数?如果渲染期间发请求会发生什么?
- 下面两段代码中
count状态的行为有何不同,为什么?
``jsx function Outer() { const [count, setCount] = useState(0); function Inner() { return <button onClick={() => setCount(count + 1)}>{count}</button>; } return <Inner />; } // vs function Inner() { ... } // 模块顶层定义 function Outer() { return <Inner />; } ``
- 你在写一个电商详情页,列出至少 4 个你会拆出的组件及拆分理由。
与其他知识点的关系
- 向前:建立在 kp-002 语法之上。
- 向后:kp-004 讲树上的数据流;kp-013 讲「传 children 而不是传 config」的组合进阶;kp-019 解释「组件函数被重复调用」在性能上的含义;kp-024 里组件开始区分客户端/服务端身份。
延伸阅读
- Your First Component:<https://react.dev/learn/your-first-component>
- Keeping Components Pure:<https://react.dev/learn/keeping-components-pure>
- Thinking in React:<https://react.dev/learn/thinking-in-react>