前言
最近在学习 React 的 props ,顺手整理了自己的一些学习代码和笔记。props 是 React 组件通信的核心,玩好了它,你写组件就会像搭积木一样顺手。今天这篇文章,就从最基础的用法开始讲起,一步步带你深入,还会结合实际代码例子来说明。顺便聊聊 React 19 前后的小变化、常见的 CSS 写法,以及那个超级好用的 children props。
文章基于我自己的学习项目,代码来自一个简单的 App,包含 Greeting、Card 和 Modal 组件。咱们边看代码边聊,绝对干货满满。
一、Props 是什么?为什么这么重要?(重点对比 state)
在 React 里,组件要正常工作,就离不开"数据"。数据主要分两类:state 和 props。这两兄弟长得有点像,但用途和性格完全不一样,搞清楚它们的关系,你写组件时才会心里有底。
用大白话总结:
| 对比项 | state(状态) | props(属性) |
|---|---|---|
| 是谁的? | 组件自己的私有数据 | 父组件传给子组件的数据 |
| 能不能改? | 可以改(用 setState 或 useState) | 只读!子组件不能直接改 |
| 改了会怎样? | 修改后组件会重新渲染 | 只有父组件重新传新值,子组件才会更新 |
| 生命周期 | 属于当前组件,组件销毁就没了 | 像"快递"一样,从父组件送到子组件 |
| 典型用途 | 表单输入值、开关状态、计数器等可变数据 | 配置信息、显示内容、回调函数等 |
用生活例子比喻最清楚
想象你家有个儿子(子组件)和老爸(父组件):
- state 就像儿子自己的零花钱:他自己赚的(或爸给的初始值),想怎么花就怎么花(修改),爸管不着。
- props 就像爸给儿子的任务和工具:爸说"今天帮我买瓶酱油,顺便带张电影票回来",儿子只能按爸给的钱和要求去办,不能自己改任务("我偏要买可乐"不行)。
如果儿子想跟爸说"我买完了"或者"我需要更多钱",那只能通过爸事先给的"传话方式"(回调函数)来沟通------这就引出了我们上次讲的"子传父"。
代码对比最直观
来看同一个需求:显示一个问候语,可以改名字和欢迎词。
用 state 的方式(数据在组件自己手里):
jsx
import { useState } from 'react';
function GreetingSelf() {
const [name, setName] = useState('陌生人');
const [message, setMessage] = useState('欢迎光临');
return (
<div>
<h1>Hello {name}</h1>
<p>{message}</p>
{/* 自己改自己,超级自由 */}
<input
placeholder="改名字"
onChange={(e) => setName(e.target.value)}
/>
<input
placeholder="改欢迎词"
onChange={(e) => setMessage(e.target.value)}
/>
</div>
);
}
这个组件完全自给自足,想改啥改啥。
用 props 的方式(数据从外面传进来):
jsx
function Greeting({ name, message }) {
return (
<div>
<h1>Hello {name}</h1>
<p>{message}</p>
{/* 这里不能改!想改得让父组件来 */}
{/* <input onChange=... /> // 没用,props 是只读的 */}
</div>
);
}
// 父组件
function App() {
const [name, setName] = useState('keji');
const [message, setMessage] = useState('欢迎来到腾讯');
return (
<div>
<Greeting name={name} message={message} />
{/* 只有这里能改,改了会重新传给子组件 */}
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={message} onChange={(e) => setMessage(e.target.value)} />
</div>
);
}
看到区别了吗?
- 用 state:组件内部自己管数据,适合独立的小部件。
- 用 props:组件变成"纯展示"工具,数据统一由父组件管理,适合复用(同一个 Greeting 可以给不同人用不同欢迎词)。
二、React 19 之前:Props 的经典玩法
在 React 18 及更早版本,props 用法已经很成熟了:
-
解构 props:直接在参数里解构,用起来最爽。
-
默认值:两种方式
- ES6 默认参数:message = "默认值"
- defaultProps(静态属性,已被废弃)
示例(旧方式):
jsxGreeting.defaultProps = { message: "欢迎来到字节" }; -
类型检查:用 prop-types 包
jsximport PropTypes from "prop-types"; Greeting.propTypes = { name: PropTypes.string.isRequired, message: PropTypes.string, };开发模式下,如果传错类型会警告,很实用。
-
传任意数据:字符串、数字、对象、函数、甚至 JSX 都可以。
三、React 19 之后:Props 的小变化(更简洁了)
React 19(2024 年 12 月稳定发布)对 props 本身的核心机制没大改,但有一些清理和改进,让代码更现代:
-
移除 propTypes 和 defaultProps:这些早在 React 15.5 就被标记废弃了,React 19 直接从 React 包里删掉。以后用的话不会报错,但完全没效果。
- 推荐:用 TypeScript 做静态类型检查,或者继续用独立的 prop-types 包(手动检查)。
- 默认值统一用 ES6 默认参数,更简洁。
-
ref 现在可以直接作为 prop 传(超级实用!) 以前,函数组件想接收 ref 必须裹一层 forwardRef:
jsx// React 18 const MyInput = React.forwardRef((props, ref) => ( <input ref={ref} {...props} /> ));React 19 后,函数组件直接收 ref,像 children 一样自然:
jsx// React 19 function MyInput({ ref, ...props }) { return <input ref={ref} {...props} />; }少了好多样板代码,官方还提供了 codemod 自动迁移。
其他 props 相关的小优化:对 Custom Elements(Web Components)的支持更好,props 会优先作为 property 传,而不是 attribute。
总之,React 19 的 props 更干净、更贴近现代 JS。如果你还在用老项目,建议升级时跑一下官方 codemod。
四、React Props 的各种"边界情况"详解:不传、传了不用、没给值会发生什么?
在实际写代码时,props 的这些"边缘情况"经常让人摸不着头脑。今天我们就一个个拆开看,结合真实代码和控制台输出,告诉你到底会发生什么,以及怎么避免坑。
1. 不传某个 prop(完全没写)
情况:父组件使用子组件时,压根没写这个 prop。
结果 :子组件收到这个 prop 的值是 undefined。
例子:
子组件 Greeting.jsx:
jsx
function Greeting({ name, message, showIcon }) {
console.log({ name, message, showIcon }); // 关键看这里
return (
<div>
<h1>Hello {name}</h1>
<p>{message}</p>
{showIcon && <span>👋</span>}
</div>
);
}
父组件这样用:
jsx
<Greeting />
控制台输出:
JavaScript
{ name: undefined, message: undefined, showIcon: undefined }
页面显示:
text
Hello // 空
// 空
结论:不传 = undefined,不会报错,但显示会很丑。
2. 传了 prop 但子组件没用(写了但没解构/使用)
情况:父组件传了值,但子组件参数里没接收它。
结果:传的值会丢失,子组件根本拿不到。
例子:
父组件:
jsx
<Greeting name="小明" message="欢迎" />
子组件故意不接收 name:
jsx
function Greeting({ message }) { // 只解构了 message,name 被忽略
console.log({ message });
return <h1>Hello {message}</h1>;
}
控制台输出:
JavaScript
{ message: "欢迎" }
name 的值"小明"直接丢了,子组件完全不知道有这个 prop。
另一种写法(用 props 对象):
jsx
function Greeting(props) {
console.log(props); // { name: "小明", message: "欢迎" }
return <h1>Hello {props.message}</h1>; // 只用了 message
}
这种情况下 props 对象里是有 name 的,但如果你没用它,就白传了。
结论:传了但没用 = 白传,浪费性能(轻微),代码易混淆。
3. 传了 prop 但没给值()
情况:写了 prop 名,但没赋值(布尔类型常见)。
结果 :React 会自动把这个 prop 的值设为 true。
例子:
父组件:
jsx
<Greeting showIcon /> // 没写 =true
<Greeting showIcon={true} /> // 显式写 true
<Greeting showIcon={false} />// 显式写 false
子组件:
jsx
function Greeting({ showIcon }) {
console.log({ showIcon });
return showIcon && <span>👋</span>;
}
三种写法的控制台输出:
JavaScript
{ showIcon: true } // <Greeting showIcon />
{ showIcon: true } // <Greeting showIcon={true} />
{ showIcon: false } // <Greeting showIcon={false} />
结论:在 JSX 中, 等价于 。这是 React 的语法糖,超级常见于开关类 prop(如 disabled、checked、selected 等)。
4. 给了默认值的情况(ES6 默认参数)
情况:不传或传 undefined 时,用默认值兜底。
例子:
jsx
function Greeting({
name = "陌生人",
message = "欢迎光临",
showIcon = false
}) {
console.log({ name, message, showIcon });
return (
<div>
<h1>Hello {name}</h1>
<p>{message}</p>
{showIcon && <span>👋</span>}
</div>
);
}
测试几种调用:
jsx
<Greeting /> // 全不传
<Greeting name={undefined} /> // 显式传 undefined
<Greeting name="" /> // 传空字符串(不会用默认)
<Greeting name="小明" />
控制台输出:
JavaScript
{ name: "陌生人", message: "欢迎光临", showIcon: false }
{ name: "陌生人", message: "欢迎光临", showIcon: false } // undefined 触发默认
{ name: "", message: "欢迎光临", showIcon: false } // 空字符串不会触发默认
{ name: "小明", message: "欢迎光临", showIcon: false }
关键点 :只有 undefined 才会触发 ES6 默认值。传 null、""、false、0 都不会触发默认值。
5. 总结表格(一目了然)
| 父组件写法 | 子组件收到值 | 是否触发默认值 | 说明 |
|---|---|---|---|
| 不写 prop | undefined | 是 | 最常见坑 |
| < Comp flag /> | true | - | 布尔 prop 的语法糖 |
| < Comp flag={true/false} /> | true / false | - | 显式控制 |
| < Comp name={undefined} /> | undefined | 是 | 显式传 undefined 也会触发默认 |
| < Comp name="" /> | ""(空字符串) | 否 | 传了具体值,就不会用默认 |
| < Comp name={null} /> | null | 否 | null 是合法值,不会触发默认 |
| 传了 prop 但子组件没接收 | 拿不到(丢失) | - | 代码 bug,建议用 TS 或 PropTypes 检查 |
五、children:最强大的 props
children 是 React 自动传给你的一个特殊 prop,代表组件标签之间的内容。它让组件超级灵活,能"插槽"任意 JSX。
Card 组件:
jsx
App.jsx
<Card className="user-card">
<h2>张三</h2>
<p>高级前端工程师</p>
<button>查看详情</button>
</Card>
Card 实现:
jsx
//Card.jsx
const Card = ({ children, className = "" }) => {
return <div className={`card ${className}`}>{children}</div>;
};
其中,{children}就是
jsx
<h2>张三</h2>
<p>高级前端工程师</p>
<button>查看详情</button>
直接把父组件传的 JSX 原样渲染出来,完美封装卡片样式。
Modal 组件(更高级的定制): 我们想让 Modal 支持自定义 header 和 footer:
jsx
//App.jsx
<Modal HeaderComponent={MyHeader} FooterComponent={MyFooter}>
<p>这是一个弹窗</p>
<p>你可以在这里显示任何JSX</p>
</Modal>
Modal 实现:
jsx
//Modal.jsx
function Modal({ HeaderComponent, FooterComponent, children }) {
return (
<div style={styles.overlay}>
<div style={styles.modal}>
<HeaderComponent />
<div style={styles.content}>{children}</div>
<FooterComponent />
</div>
</div>
);
}
这里 children 就是弹窗正文内容,而 Header/Footer 是通过 props 传组件,实现高度定制。
children 的威力在于:它让你的组件像"容器"一样,能包裹任意内容。Ant Design、Material UI 的很多组件都靠它实现灵活性。
六、React 中常见的 CSS 样式写法
React 是"CSS in JS"的鼻祖,样式写法灵活多变。来看几种常见方式,各有优缺点:
-
内联样式(style 对象) ------ 最简单,直接在 JSX 里写
jsx<h2 style={{ margin: 0, color: "blue" }}>自定义标题</h2>适合快速原型或动态样式(比如根据 props 变色)。缺点:没法用伪类(如 :hover),写多了很乱。
-
单独 CSS 文件 + className ------ 传统方式,最常见 看 Card.jsx 和 Card.css:
jsximport "./Card.css"; const Card = ({ children, className = "" }) => { return <div className={`card ${className}`}>{children}</div>; };CSS 文件里写:
CSS.card { background-color: #ffffff; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* ... */ } .card:hover { transform: translateY(-4px); /* 悬停效果 */ }优点:支持所有 CSS 特性(伪类、媒体查询),易维护。缺点:类名冲突风险(可以用 CSS Modules 解决)。
-
CSS in JS(对象样式)
jsxconst styles = { overlay: { backgroundColor: "rgba(0,0,0,0.5)", /* ... */ }, modal: { backgroundColor: "white", /* ... */ }, }; <div style={styles.overlay}>...</div>优点:样式作用域天然隔离,动态样式超方便。缺点:运行时开销稍大,伪类支持差(需要额外库如 styled-components)。
-
其他流行方案:
- styled-components 或 emotion:写模板字符串,兼顾作用域和伪类。
- Tailwind CSS:utility-first,直接在 className 里堆类。
- CSS Modules:自动生成唯一类名,避免冲突。
个人建议:小项目内联或单独 CSS 够用,中大型项目推荐 CSS Modules + Tailwind,或者 styled-components。
最后总结
props 是 React 的灵魂,掌握它你就掌握了组件通信。默认值、类型检查、children 这些细节用好了,代码会优雅很多。React 19 把一些老古董清理掉,鼓励我们写更现代的代码(ES6 默认 + TS)。
如果你也在学 React,建议自己动手搭几个组件玩玩 props 和 children,感觉完全不一样。