讲解
组件就是返回 JSX 的函数,名字必须大写(小写会被编译成 HTML 标签字符串——<button> 是按钮元素,<Button> 才是你的组件)。拆组件的时机:一段 UI 被用到第二次,或者一个组件函数超过一屏,就该拆了。
Props 是组件的参数,也是父子通信的主通道:父组件 <Avatar user={user} size={80}>,子组件用解构接收 function Avatar({ user, size = 40 })——默认参数就是默认值。Props 可以是任何东西:字符串、数字、对象、数组、函数,甚至 JSX(通过 children 传递:<Card><h1>标题</h1></Card>,子组件 {children} 处渲染)。
最重要的纪律:props 只读。子组件永远不改 props——想改,通知父组件改(父传下来的回调函数就是干这个的)。这保证了「数据自上而下」的单向流动,任何组件的界面都能由它的 props 和 state 唯一确定,调试时不用满世界找「谁改了数据」。
children 是 props 里的特例但威力巨大:布局组件(Card、Modal、Tabs)都靠它做「内容分发」——组件管外壳和行为,内容完全由使用方决定。再进一步,props 也可以传渲染函数(render props 模式),不过在 hooks 时代大部分场景已被自定义 hook 取代,了解即可。
示例
验证 props 的传递、默认值和只读纪律(在本教程构建时被真实执行):
import assert from 'node:assert/strict';
// 组件 = 函数;props = 参数对象(带默认值)
function Avatar({ user, size = 40 }) {
return '<img src="' + user.avatar + '" width="' + size + '" alt="' + user.name + '" />';
}
function Badge({ children, color = 'gray' }) {
return '<span class="' + color + '">' + children + '</span>';
}
const user = { name: '小明', avatar: '/a.png' };
// 默认值生效
assert.strictEqual(Avatar({ user }), '<img src="/a.png" width="40" alt="小明" />');
assert.strictEqual(Avatar({ user, size: 80 }), '<img src="/a.png" width="80" alt="小明" />');
// children 就是 props.children
assert.strictEqual(Badge({ children: 'VIP', color: 'gold' }), '<span class="gold">VIP</span>');
// 组合:组件可以当 props 的值(JSX 元素就是普通值)
function Card({ header, children }) {
return '<div class="card"><header>' + header + '</header><main>' + children + '</main></div>';
}
const page = Card({ header: Badge({ children: '公告' }), children: '本周五发布新版本' });
assert.ok(page.includes('<span class="gray">公告</span>'));
assert.ok(page.includes('本周五发布新版本'));
console.log('组合渲染:', page);
console.log('props 只读:子组件想改数据,只能调用父组件传下来的回调');
真实的组件写法:
function Avatar({ user, size = 40 }) {
return <img src={user.avatar} width={size} alt={user.name} />;
}
function Card({ title, children }) {
return (
<div className="card">
<header>{title}</header>
<main>{children}</main>
</div>
);
}
export default function Page() {
const user = { name: '小明', avatar: '/a.png' };
return (
<Card title="个人资料">
<Avatar user={user} size={80} />
<p>{user.name}</p>
</Card>
);
}
常见坑
- 组件名小写:
<avatar>被当成未知 HTML 标签原样输出,控制台有警告;组件一律 PascalCase。 - 子组件改 props:props 是父组件的数据,改了不生效还报错;要改就调用父传下来的回调。
- 默认值写错位置:
size = 40只对 undefined 生效,父组件显式传 null 时默认值不兜底,需要的话函数内再判空。 - props 层层透传:隔三四层纯转手就该考虑组合(children)或 Context,后面有专章。
- key 当普通 props 读:key 是 React 内部用的,组件内读不到 props.key,需要就换个名字再传一份。
小结
组件是大写函数,props 是只读参数;children 分发内容,回调函数实现「子通知父」。下一章渲染列表并理解 key 的真正作用。