
06、React Props
从入门到企业级实战,一篇文章彻底搞懂 React 组件通信的基石
在 React 的世界里,组件是构建用户界面的基本单位。而组件之间如何对话、如何传递数据、如何协同工作,答案几乎都绕不开一个核心概念------Props。
无论是刚入门的小白,还是工作多年的前端工程师,Props 都是每天都在使用的东西。但真正能把 Props 讲透彻、用到位、在面试中对答如流的人,其实并不多。
这篇文章将从真实项目中的痛点出发,带你从零到一彻底掌握 React Props,涵盖核心概念、演进历史、基础用法、企业级实战、竞品对比、常用场景以及面试高频考点,让你看完就能直接上手。
文章目录
- [06、React Props](#06、React Props)
-
- 一、痛点场景:你是否也被这些问题困扰过?
-
- [1.1 Prop Drilling:数据要穿越八层组件](#1.1 Prop Drilling:数据要穿越八层组件)
- [1.2 子组件想修改父组件的数据,怎么办?](#1.2 子组件想修改父组件的数据,怎么办?)
- [1.3 组件复用困难,每个地方都要写一遍](#1.3 组件复用困难,每个地方都要写一遍)
- [1.4 类型不明确,传错了才发现](#1.4 类型不明确,传错了才发现)
- [二、痛点的解决方案:认识 Props](#二、痛点的解决方案:认识 Props)
-
- [2.1 专业解释](#2.1 专业解释)
- [2.2 大白话解释](#2.2 大白话解释)
- [2.3 生活案例](#2.3 生活案例)
- [三、为什么要用 Props?](#三、为什么要用 Props?)
-
- [3.1 组件复用的基础](#3.1 组件复用的基础)
- [3.2 单向数据流保证可预测性](#3.2 单向数据流保证可预测性)
- [3.3 组件解耦](#3.3 组件解耦)
- [3.4 性能优化的基础](#3.4 性能优化的基础)
- [四、Props 是怎么演进过来的?](#四、Props 是怎么演进过来的?)
-
- [4.1 上古时代:React.createClass(2013)](#4.1 上古时代:React.createClass(2013))
- [4.2 中古时代:ES6 Class 组件(2015)](#4.2 中古时代:ES6 Class 组件(2015))
- [4.3 近代:函数组件 + Hooks(2018)](#4.3 近代:函数组件 + Hooks(2018))
- [4.4 现代:TypeScript 类型定义(2020 至今)](#4.4 现代:TypeScript 类型定义(2020 至今))
- [4.5 React 19 的变化(2024)](#4.5 React 19 的变化(2024))
- [五、怎么用 Props?](#五、怎么用 Props?)
-
- [5.1 基础用法四步走](#5.1 基础用法四步走)
-
- 第一步:父组件定义数据
- [第二步:通过 JSX 属性传递 props](#第二步:通过 JSX 属性传递 props)
- [第三步:子组件接收 props](#第三步:子组件接收 props)
- 第四步:子组件渲染使用
- [5.2 子传父:通过回调函数](#5.2 子传父:通过回调函数)
- [5.3 children:特殊的 props](#5.3 children:特殊的 props)
- [5.4 展开运算符:批量传递 props](#5.4 展开运算符:批量传递 props)
- [六、企业项目中如何使用 Props?](#六、企业项目中如何使用 Props?)
-
- [6.1 组件设计原则:容器组件与展示组件分离](#6.1 组件设计原则:容器组件与展示组件分离)
- [6.2 解决 Prop Drilling:组合优于继承](#6.2 解决 Prop Drilling:组合优于继承)
- [6.3 Render Props 模式](#6.3 Render Props 模式)
- [6.4 性能优化:避免不必要的重渲染](#6.4 性能优化:避免不必要的重渲染)
- [6.5 类型安全:TypeScript 最佳实践](#6.5 类型安全:TypeScript 最佳实践)
- [七、竞品对比:Props vs State vs Context vs Redux](#七、竞品对比:Props vs State vs Context vs Redux)
-
- [7.1 选型决策树](#7.1 选型决策树)
- [7.2 React Props vs Vue Props](#7.2 React Props vs Vue Props)
- 八、常用场景大全
-
- [8.1 场景一:父传子数据传递](#8.1 场景一:父传子数据传递)
- [8.2 场景二:子传父回调通信](#8.2 场景二:子传父回调通信)
- [8.3 场景三:组件复用配置化](#8.3 场景三:组件复用配置化)
- [8.4 场景四:条件渲染控制](#8.4 场景四:条件渲染控制)
- [8.5 场景五:样式与主题定制](#8.5 场景五:样式与主题定制)
- [8.6 场景六:表单数据双向绑定](#8.6 场景六:表单数据双向绑定)
- 九、面试官高频面试题
-
- [面试题 1:什么是 Props?它有什么特点?](#面试题 1:什么是 Props?它有什么特点?)
- [面试题 2:Props 和 State 的区别是什么?](#面试题 2:Props 和 State 的区别是什么?)
- [面试题 3:为什么 Props 是不可变的?](#面试题 3:为什么 Props 是不可变的?)
- [面试题 4:子组件如何修改父组件的数据?](#面试题 4:子组件如何修改父组件的数据?)
- [面试题 5:什么是 Prop Drilling?如何解决?](#面试题 5:什么是 Prop Drilling?如何解决?)
- [面试题 6:React.memo 有什么用?它的原理是什么?](#面试题 6:React.memo 有什么用?它的原理是什么?)
- [面试题 7:defaultProps 和参数默认值有什么区别?](#面试题 7:defaultProps 和参数默认值有什么区别?)
- [面试题 8:children 是什么?有哪些用法?](#面试题 8:children 是什么?有哪些用法?)
- [面试题 9:如何对 Props 进行类型检查?](#面试题 9:如何对 Props 进行类型检查?)
- [面试题 10:React 19 对 Props 做了哪些改变?](#面试题 10:React 19 对 Props 做了哪些改变?)
- 十、总结与最佳实践清单
-
- [10.1 核心要点回顾](#10.1 核心要点回顾)
- [10.2 企业级最佳实践清单](#10.2 企业级最佳实践清单)
- [10.3 学习路径建议](#10.3 学习路径建议)
一、痛点场景:你是否也被这些问题困扰过?
1.1 Prop Drilling:数据要穿越八层组件
想象一下这样的场景:你在一个中后台项目中,顶层的 App 组件获取了用户信息 userInfo,而最底层的一个 UserAvatar 组件需要展示用户头像。中间隔着 Layout、Sidebar、Menu、MenuItem 等四五层组件。
为了把 userInfo 传到最底层,你不得不让每一层中间组件都接收这个 props,再往下传。这些中间组件根本用不到 userInfo,它们只是充当了"快递员"的角色。
javascript
// App.jsx - 顶层组件
function App() {
const [userInfo, setUserInfo] = useState({ name: '张三', avatar: '/avatar.png' });
return <Layout userInfo={userInfo} />;
}
// Layout.jsx - 只是转发,不使用 userInfo
function Layout({ userInfo }) {
return (
<div>
<Sidebar userInfo={userInfo} />
<Content />
</div>
);
}
// Sidebar.jsx - 还是转发
function Sidebar({ userInfo }) {
return <Menu userInfo={userInfo} />;
}
// Menu.jsx - 继续转发
function Menu({ userInfo }) {
return <MenuItem userInfo={userInfo} />;
}
// MenuItem.jsx - 终于用到了
function MenuItem({ userInfo }) {
return <UserAvatar avatar={userInfo.avatar} />;
}
这就是经典的 Prop Drilling(属性钻探) 问题。代码冗余、维护困难、重构痛苦------改一个字段名要改八层文件。

1.2 子组件想修改父组件的数据,怎么办?
新手常犯的错误:直接在子组件里修改 props。
javascript
// 错误示范
function Child({ count }) {
// 报错:props 是只读的,不能直接修改
count = count + 1;
return <div>{count}</div>;
}
然后你会困惑:"我明明改了,为什么不生效?"或者"为什么控制台报 warning?"
1.3 组件复用困难,每个地方都要写一遍
你写了一个 Button 组件,在 A 页面要红色,在 B 页面要蓝色,在 C 页面要大号字体。如果不通过 props 配置化,你就得写三个 Button 组件,或者在组件内部写一堆 if-else 判断当前是哪个页面。
1.4 类型不明确,传错了才发现
父组件传了一个 string 类型的 age,子组件当成 number 来做数学运算,结果 NaN。没有类型检查,运行时才报错,调试半天。
这些痛点,几乎每一个 React 开发者都遇到过。而解决这些问题的钥匙,就是正确理解和使用 Props。
二、痛点的解决方案:认识 Props
2.1 专业解释
Props (Properties 的缩写)是 React 组件之间传递数据的机制。父组件通过 JSX 属性的形式将数据传递给子组件,子组件通过函数参数(函数组件)或 this.props(类组件)接收这些数据。
Props 的核心特征:
- 单向数据流:数据只能从父组件流向子组件,不能反向流动
- 只读性:子组件不能直接修改 props,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
- 可传递任意类型:字符串、数字、布尔值、数组、对象、函数、甚至 JSX 元素都可以作为 props 传递

2.2 大白话解释
说人话:Props 就像是组件的"快递包裹"。
- 父组件是"寄件人",把要传递的数据打包成包裹(props)
- 子组件是"收件人",签收包裹后可以使用里面的东西
- 但收件人不能修改包裹里的东西(只读),如果需要改,得联系寄件人(父组件)去改
- 包裹里可以装任何东西:文字、数字、列表、甚至是一个"操作指令"(回调函数)
2.3 生活案例
去餐厅吃饭的场景:
- 你(父组件)向服务员(子组件)下单,说"我要一份牛肉面,不要香菜,多加辣"
- 这些要求(牛肉面、不要香菜、多加辣)就是 props
- 服务员收到订单后,按照要求去后厨传达,但服务员不能私自更改你的订单(只读)
- 如果服务员发现你点的菜卖完了,他会回来告诉你(通过回调函数通知父组件),由你来决定换什么菜
- 同一家餐厅(同一个组件),不同的顾客(不同的父组件)可以点不同的菜(传不同的 props),这就是组件的复用性
三、为什么要用 Props?
3.1 组件复用的基础
Props 让组件变得可配置。同一个 Button 组件,通过传入不同的 props,可以变成不同的样子:
jsx
<Button type="primary" size="large">提交</Button>
<Button type="danger" size="small">删除</Button>
<Button type="default" disabled>禁用</Button>
如果没有 props,你需要为每种按钮写一个单独的组件,代码量爆炸。
3.2 单向数据流保证可预测性
Props 的只读性和单向数据流,让数据的变化路径变得清晰可追溯。当界面出现问题时,你只需要沿着组件树向上找,就能定位到数据的源头。这比双向绑定的"谁都能改"模式更容易调试。
3.3 组件解耦
子组件不需要关心数据从哪里来、怎么来的,它只需要声明"我需要什么数据"(通过 props 接口),父组件负责提供。这种关注点分离,让组件可以独立开发、独立测试。
3.4 性能优化的基础
因为 props 是不可变的,React 可以通过浅比较(shallow comparison)快速判断 props 是否变化,从而决定是否重新渲染组件。React.memo、useMemo 等优化手段,都建立在 props 不可变这个前提之上。
四、Props 是怎么演进过来的?
React 从 2013 年发布至今,Props 的写法经历了几次重大变革。了解这段历史,能帮你看懂老项目的代码,也能理解为什么现在推荐函数组件的写法。

4.1 上古时代:React.createClass(2013)
最早的 React 使用 createClass 来创建组件,props 通过 this.props 访问,默认值用 getDefaultProps 方法:
javascript
var HelloMessage = React.createClass({
getDefaultProps: function() {
return {
name: '默认名称'
};
},
render: function() {
return <h1>Hello {this.props.name}</h1>;
}
});
这种写法的问题:冗长、this 绑定麻烦、不支持 ES6 语法。
4.2 中古时代:ES6 Class 组件(2015)
React 0.13 引入了 ES6 Class 语法,props 的写法变得更现代化:
javascript
class HelloMessage extends React.Component {
render() {
return <h1>Hello {this.props.name}</h1>;
}
}
HelloMessage.defaultProps = {
name: '默认名称'
};
同时引入了 PropTypes 进行类型检查:
javascript
import PropTypes from 'prop-types';
HelloMessage.propTypes = {
name: PropTypes.string.isRequired
};
4.3 近代:函数组件 + Hooks(2018)
React 16.8 引入 Hooks 后,函数组件成为主流。props 直接作为函数参数传入,可以用解构赋值简化:
javascript
function HelloMessage({ name = '默认名称' }) {
return <h1>Hello {name}</h1>;
}
默认值直接用 ES6 的参数默认值语法,不再需要 defaultProps。
4.4 现代:TypeScript 类型定义(2020 至今)
TypeScript 的普及让 props 的类型定义更加严谨:
typescript
interface HelloMessageProps {
name: string;
age?: number;
}
function HelloMessage({ name, age = 18 }: HelloMessageProps) {
return (
<div>
<h1>Hello {name}</h1>
<p>Age: {age}</p>
</div>
);
}
4.5 React 19 的变化(2024)
React 19 对 props 做了两个重要调整:
ref成为普通 props :函数组件中可以直接从 props 中解构ref,不再需要forwardRef- 移除函数组件的
defaultProps和propTypes:推荐使用参数默认值和 TypeScript
tsx
// React 19 新写法
function MyInput({ ref, placeholder = '请输入' }) {
return <input ref={ref} placeholder={placeholder} />;
}
五、怎么用 Props?
5.1 基础用法四步走

第一步:父组件定义数据
javascript
// Parent.jsx
import { useState } from 'react';
function Parent() {
const [message, setMessage] = useState('Hello from Parent');
return <Child text={message} />;
}
第二步:通过 JSX 属性传递 props
javascript
// 传递字符串
<Child text="Hello" />
// 传递变量
<Child text={message} />
// 传递数字
<Child count={10} />
// 传递布尔值
<Child visible={true} />
// 简写(等价于 visible={true})
<Child visible />
// 传递对象
<Child user={{ name: '张三', age: 25 }} />
// 传递数组
<Child list={[1, 2, 3]} />
// 传递函数
<Child onClick={() => console.log('clicked')} />
// 传递 JSX(children)
<Child>
<p>这是 children</p>
</Child>
第三步:子组件接收 props
javascript
// 方式一:直接使用 props 参数
function Child(props) {
return <div>{props.text}</div>;
}
// 方式二:解构赋值(推荐)
function Child({ text }) {
return <div>{text}</div>;
}
// 方式三:解构 + 默认值(更推荐)
function Child({ text = '默认文本', count = 0 }) {
return (
<div>
<p>{text}</p>
<p>{count}</p>
</div>
);
}
// 方式四:剩余参数收集
function Child({ text, ...rest }) {
// rest 包含除了 text 之外的所有 props
return <div {...rest}>{text}</div>;
}
第四步:子组件渲染使用
javascript
function UserCard({ name, age, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>年龄:{age} 岁</p>
</div>
);
}
5.2 子传父:通过回调函数
Props 是单向的,子组件不能直接修改父组件的数据。但子组件可以通过调用父组件传下来的回调函数,通知父组件去修改:
javascript
// Parent.jsx
function Parent() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(prev => prev + 1);
};
return (
<div>
<p>当前计数:{count}</p>
<Child onIncrement={handleIncrement} />
</div>
);
}
// Child.jsx
function Child({ onIncrement }) {
return (
<button onClick={onIncrement}>
点击增加
</button>
);
}
如果子组件需要传递参数给父组件:
javascript
// Child.jsx
function Child({ onSelect }) {
const items = ['苹果', '香蕉', '橙子'];
return (
<ul>
{items.map(item => (
<li key={item} onClick={() => onSelect(item)}>
{item}
</li>
))}
</ul>
);
}
// Parent.jsx
function Parent() {
const [selected, setSelected] = useState('');
const handleSelect = (item) => {
setSelected(item);
console.log('选中了:', item);
};
return (
<div>
<p>已选择:{selected}</p>
<Child onSelect={handleSelect} />
</div>
);
}
5.3 children:特殊的 props
children 是一个特殊的 prop,代表组件标签之间的内容:
javascript
// 使用方式
<Card>
<h2>标题</h2>
<p>这是卡片内容</p>
</Card>
// Card 组件接收 children
function Card({ children, title }) {
return (
<div className="card">
{title && <h3 className="card-title">{title}</h3>}
<div className="card-body">{children}</div>
</div>
);
}
children 可以是任何东西:字符串、数字、JSX 元素、数组、甚至函数(Render Props 模式)。
5.4 展开运算符:批量传递 props
当需要传递的 props 很多时,可以用展开运算符简化:
javascript
function Parent() {
const config = {
name: '张三',
age: 25,
gender: '男',
address: '广州市天河区'
};
// 等价于 <User name={config.name} age={config.age} ... />
return <User {...config} />;
}
六、企业项目中如何使用 Props?
理论学完了,我们来看真实企业项目中的 Props 使用模式和最佳实践。

6.1 组件设计原则:容器组件与展示组件分离
企业项目中通常遵循"容器组件 + 展示组件"的模式:
- 容器组件(Container):负责数据获取、状态管理、业务逻辑,通过 props 把数据传给展示组件
- 展示组件(Presentational):只负责根据 props 渲染 UI,不关心数据来源
typescript
// containers/UserListContainer.tsx - 容器组件
import { useState, useEffect } from 'react';
import UserList from '../components/UserList';
interface User {
id: number;
name: string;
email: string;
}
export default function UserListContainer() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
setLoading(true);
const res = await fetch('/api/users');
const data = await res.json();
setUsers(data);
} catch (err) {
setError('获取用户列表失败');
} finally {
setLoading(false);
}
};
const handleDelete = async (id: number) => {
await fetch(`/api/users/${id}`, { method: 'DELETE' });
setUsers(prev => prev.filter(u => u.id !== id));
};
return (
<UserList
users={users}
loading={loading}
error={error}
onDelete={handleDelete}
onRefresh={fetchUsers}
/>
);
}
typescript
// components/UserList.tsx - 展示组件
interface User {
id: number;
name: string;
email: string;
}
interface UserListProps {
users: User[];
loading: boolean;
error: string | null;
onDelete: (id: number) => void;
onRefresh: () => void;
}
export default function UserList({
users,
loading,
error,
onDelete,
onRefresh
}: UserListProps) {
if (loading) return <div className="loading">加载中...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="user-list">
<button onClick={onRefresh}>刷新</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
<button onClick={() => onDelete(user.id)}>删除</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
6.2 解决 Prop Drilling:组合优于继承
面对 Prop Drilling,不一定非要用 Context 或 Redux。很多时候,组件组合(Composition) 是更优雅的解决方案:
tsx
// 不好的做法:层层传递
function Page() {
const user = useUser();
return <Layout user={user} />;
}
function Layout({ user }) {
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
return <UserPanel user={user} />;
}
// 好的做法:组合模式
function Page() {
const user = useUser();
return (
<Layout
sidebar={
<Sidebar>
<UserPanel user={user} />
</Sidebar>
}
/>
);
}
function Layout({ sidebar, content }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
);
}
通过把已经组装好的组件作为 children 或具名 prop 传入,中间层组件完全不需要知道 user 的存在。
6.3 Render Props 模式
Render Props 是一种通过函数类型的 prop 来共享代码的模式:
tsx
// MouseTracker.tsx - 追踪鼠标位置的通用组件
import { useState, useEffect } from 'react';
interface Position {
x: number;
y: number;
}
interface MouseTrackerProps {
render: (position: Position) => React.ReactNode;
}
export default function MouseTracker({ render }: MouseTrackerProps) {
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return <div>{render(position)}</div>;
}
// 使用方式
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<div>
鼠标位置:({x}, {y})
</div>
)}
/>
);
}
6.4 性能优化:避免不必要的重渲染
企业项目中,props 的使用方式直接影响性能。以下是几个关键优化点:
tsx
// 1. 使用 React.memo 包裹纯展示组件
const UserCard = React.memo(function UserCard({ user }: { user: User }) {
return <div>{user.name}</div>;
});
// 2. 避免在渲染中创建新对象/新函数(会导致 memo 失效)
// 不好的写法
function Parent() {
const [count, setCount] = useState(0);
return (
<Child
config={{ theme: 'dark' }} // 每次渲染都是新对象
onClick={() => doSomething()} // 每次渲染都是新函数
/>
);
}
// 好的写法
function Parent() {
const [count, setCount] = useState(0);
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleClick = useCallback(() => doSomething(), []);
return <Child config={config} onClick={handleClick} />;
}
// 3. 大列表使用虚拟滚动,避免传递大量 props
6.5 类型安全:TypeScript 最佳实践
typescript
// 定义 Props 类型(推荐用 interface)
interface ButtonProps {
/** 按钮类型 */
type?: 'primary' | 'default' | 'danger';
/** 按钮尺寸 */
size?: 'small' | 'medium' | 'large';
/** 点击事件 */
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
/** 是否禁用 */
disabled?: boolean;
/** 子元素 */
children: React.ReactNode;
}
// 函数组件 + 泛型(更严谨)
function Button({
type = 'default',
size = 'medium',
disabled = false,
onClick,
children
}: ButtonProps) {
return (
<button
className={`btn btn-${type} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// 继承原生元素属性
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
}
function Button({ variant = 'primary', ...rest }: ButtonProps) {
return <button className={`btn-${variant}`} {...rest} />;
}
七、竞品对比:Props vs State vs Context vs Redux
在 React 生态中,数据管理有多种方案。很多新手分不清什么时候该用什么。下面这张表帮你一次性理清:
| 对比维度 | Props | State | Context | Redux / Zustand |
|---|---|---|---|---|
| 数据来源 | 父组件传入 | 组件内部创建 | Provider 提供 | 全局 Store 管理 |
| 可变性 | 只读,不可修改 | 可变,setState 更新 | 通过 Provider value 更新 | 通过 dispatch / action 更新 |
| 作用范围 | 父子组件之间 | 当前组件内部 | 组件树任意层级 | 全局任意组件 |
| 适用场景 | 组件配置、数据传递、回调通信 | 组件内部交互状态(输入框、开关、弹窗) | 主题、语言、用户信息等跨层级共享 | 大型应用全局状态、复杂状态逻辑 |
| 学习成本 | 低 | 低 | 中 | 高(Redux)/ 中(Zustand) |
| 性能影响 | 浅比较即可优化 | 直接触发重渲染 | value 变化导致所有 Consumer 重渲染 | 可精确订阅,按需重渲染 |
| 调试难度 | 低,数据流清晰 | 低,局部可控 | 中,需追踪 Provider | 中高,但有 DevTools 支持 |
| 代码量 | 少 | 少 | 中等 | 多(Redux)/ 少(Zustand) |
| 典型反模式 | Prop Drilling | 把所有状态都放 state | 滥用 Context 代替 Props | 什么状态都往全局放 |
7.1 选型决策树
需要共享数据吗?
├── 不需要 → 用 State(组件内部状态)
└── 需要
├── 只在父子之间传递?
│ └── 是 → 用 Props
└── 需要跨多层或多分支共享?
├── 数据变化不频繁(主题、语言、用户信息)?
│ └── 是 → 用 Context
└── 数据复杂、变化频繁、需要中间件?
└── 是 → 用 Redux / Zustand
7.2 React Props vs Vue Props
如果你有 Vue 背景,转到 React 时最容易困惑的就是 Props 的差异:
| 对比维度 | React Props | Vue Props |
|---|---|---|
| 声明方式 | 函数参数 / this.props,无需声明 | 必须在 props 选项或 defineProps 中声明 |
| 子传父 | 传递回调函数作为 prop | $emit 触发事件,父组件 @event 监听 |
| 类型检查 | TypeScript / PropTypes | 内置类型校验(type、required、default、validator) |
| 默认值 | 函数参数默认值 / defaultProps | default 字段 |
| 双向绑定 | 不支持,需手动传回调 | 支持 v-model 语法糖 |
| 事件命名 | onClick、onChange(驼峰) |
@click、@change(短横线) |
| 插槽 | children prop |
slot 具名插槽 |
| 设计哲学 | 一切皆 JS,用函数和组合表达 | 模板语法,框架提供更多约定 |
vue
<!-- Vue 写法 -->
<template>
<Child :message="msg" @update="handleUpdate" />
</template>
<script setup>
import { ref } from 'vue';
const msg = ref('Hello');
const handleUpdate = (val) => { msg.value = val; };
</script>
jsx
// React 写法
function Parent() {
const [msg, setMsg] = useState('Hello');
return <Child message={msg} onUpdate={setMsg} />;
}
八、常用场景大全

8.1 场景一:父传子数据传递
最基础的用法,把数据从父组件传到子组件展示:
tsx
function ProductList() {
const products = [
{ id: 1, name: 'iPhone 15', price: 5999 },
{ id: 2, name: 'MacBook Pro', price: 14999 },
];
return (
<div>
{products.map(product => (
<ProductCard
key={product.id}
name={product.name}
price={product.price}
/>
))}
</div>
);
}
8.2 场景二:子传父回调通信
子组件通过回调函数通知父组件发生了什么:
tsx
// 搜索框子组件
function SearchInput({ onSearch }) {
const [keyword, setKeyword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSearch(keyword);
};
return (
<form onSubmit={handleSubmit}>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="搜索..."
/>
<button type="submit">搜索</button>
</form>
);
}
// 父组件使用
function Page() {
const handleSearch = (keyword) => {
console.log('搜索关键词:', keyword);
// 调用搜索 API
};
return <SearchInput onSearch={handleSearch} />;
}
8.3 场景三:组件复用配置化
通过 props 让同一个组件适应不同场景:
tsx
interface ModalProps {
visible: boolean;
title: string;
confirmText?: string;
cancelText?: string;
onConfirm: () => void;
onCancel: () => void;
children: React.ReactNode;
}
function Modal({
visible,
title,
confirmText = '确定',
cancelText = '取消',
onConfirm,
onCancel,
children
}: ModalProps) {
if (!visible) return null;
return (
<div className="modal-overlay">
<div className="modal">
<h3>{title}</h3>
<div className="modal-body">{children}</div>
<div className="modal-footer">
<button onClick={onCancel}>{cancelText}</button>
<button className="primary" onClick={onConfirm}>{confirmText}</button>
</div>
</div>
</div>
);
}
// 不同场景复用同一个 Modal
<Modal
visible={deleteVisible}
title="确认删除"
confirmText="删除"
onConfirm={handleDelete}
onCancel={() => setDeleteVisible(false)}
>
<p>确定要删除这条记录吗?此操作不可恢复。</p>
</Modal>
<Modal
visible={infoVisible}
title="提示"
confirmText="我知道了"
onConfirm={() => setInfoVisible(false)}
onCancel={() => setInfoVisible(false)}
>
<p>操作成功!</p>
</Modal>
8.4 场景四:条件渲染控制
通过 props 控制组件的显示逻辑:
tsx
function Page({ isAdmin, isVip, user }) {
return (
<div>
{isAdmin && <AdminPanel />}
{isVip ? <VipContent /> : <FreeContent />}
{user && <UserProfile user={user} />}
</div>
);
}
8.5 场景五:样式与主题定制
tsx
interface BadgeProps {
count: number;
color?: 'red' | 'blue' | 'green';
max?: number;
}
function Badge({ count, color = 'red', max = 99 }: BadgeProps) {
const display = count > max ? `${max}+` : count;
return <span className={`badge badge-${color}`}>{display}</span>;
}
// 使用
<Badge count={5} />
<Badge count={150} color="blue" max={99} />
8.6 场景六:表单数据双向绑定
React 没有 v-model,需要通过 value + onChange 实现:
tsx
function Form() {
const [form, setForm] = useState({ username: '', email: '' });
const handleChange = (field) => (e) => {
setForm(prev => ({ ...prev, [field]: e.target.value }));
};
return (
<form>
<Input
label="用户名"
value={form.username}
onChange={handleChange('username')}
/>
<Input
label="邮箱"
value={form.email}
onChange={handleChange('email')}
/>
</form>
);
}
function Input({ label, value, onChange }) {
return (
<div className="form-item">
<label>{label}</label>
<input value={value} onChange={onChange} />
</div>
);
}
九、面试官高频面试题

面试题 1:什么是 Props?它有什么特点?
参考答案:
Props 是 React 中组件之间传递数据的机制,全称 Properties。父组件通过 JSX 属性将数据传递给子组件,子组件通过函数参数或 this.props 接收。
核心特点:
- 单向数据流:数据只能从父到子,不能反向
- 只读性:子组件不能修改 props,必须像纯函数一样保护 props
- 可传递任意类型:包括基本类型、对象、数组、函数、JSX 等
- 触发重渲染:props 变化会导致组件重新渲染
面试题 2:Props 和 State 的区别是什么?
参考答案:
| 维度 | Props | State |
|---|---|---|
| 来源 | 父组件传入 | 组件内部创建 |
| 可变性 | 只读,不可修改 | 可变,通过 setState 更新 |
| 作用 | 组件间通信、配置化 | 组件内部状态管理 |
| 重渲染 | 父组件传入新值时触发 | setState 调用时触发 |
| 类比 | 函数参数 | 函数内部变量 |
一句话总结:Props 是外部传入的配置,State 是内部管理的状态。
面试题 3:为什么 Props 是不可变的?
参考答案:
- 可预测性:单向数据流让状态变更路径清晰可追溯,避免"幽灵更新",简化调试
- 性能优化 :不可变性让 React 可以通过浅比较高效判断 props 是否变化,配合
React.memo实现跳过渲染 - 可维护性:降低组件耦合度,子组件不会意外修改父组件状态
- 纯函数理念:React 组件应像纯函数一样,相同的 props 输入总是产生相同的输出
面试题 4:子组件如何修改父组件的数据?
参考答案:
子组件不能直接修改父组件的数据,但可以通过回调函数的方式通知父组件修改:
- 父组件定义一个修改状态的函数
- 将这个函数作为 prop 传递给子组件
- 子组件在需要时调用这个函数,并传入参数
- 父组件的函数执行,更新自身 state
- state 变化后通过 props 重新传给子组件,完成更新闭环
面试题 5:什么是 Prop Drilling?如何解决?
参考答案:
Prop Drilling(属性钻探)是指数据需要通过多层中间组件层层传递才能到达目标组件,而中间组件并不使用这些数据,只是转发。
解决方案:
- 组件组合(Composition):将已经组装好的组件作为 children 传入,中间层不感知数据
- Context API:适合主题、语言、用户信息等跨层级共享的数据
- 状态管理库:Redux、Zustand 等,适合复杂的全局状态管理
- 合理拆分组件:避免过深的组件嵌套
面试题 6:React.memo 有什么用?它的原理是什么?
参考答案:
React.memo 是一个高阶组件,用于优化函数组件的性能。它会对组件的 props 进行浅比较,如果 props 没有变化,就跳过组件的重新渲染。
原理:
React.memo包裹组件后,会缓存上一次的渲染结果- 每次父组件重渲染时,对新 props 和旧 props 做浅比较(只比较第一层引用)
- 如果相同,直接复用缓存的渲染结果,不执行组件函数
- 如果不同,重新渲染组件
注意事项:
- 如果 props 中包含对象或函数,每次渲染都是新引用,
React.memo会失效 - 需要配合
useMemo和useCallback使用才能达到最佳效果 React.memo不是万能的,过度使用反而增加比较开销
面试题 7:defaultProps 和参数默认值有什么区别?
参考答案:
defaultProps是类组件时代的写法,在 React 19 中已从函数组件中移除- 参数默认值是 ES6 语法,更简洁,是现在的推荐写法
- 功能上基本一致,都是为 props 提供默认值
javascript
// 旧写法
function Child({ name }) {
return <div>{name}</div>;
}
Child.defaultProps = { name: '默认' };
// 新写法(推荐)
function Child({ name = '默认' }) {
return <div>{name}</div>;
}
面试题 8:children 是什么?有哪些用法?
参考答案:
children 是一个特殊的 prop,代表组件开始标签和结束标签之间的内容。
用法:
- 普通内容:文本、JSX 元素
- 函数(Render Props):传递一个函数,实现代码复用
- React.Children 工具 :
React.Children.map、React.Children.toArray等操作 children - 具名插槽替代:通过多个 prop 传递不同位置的内容
面试题 9:如何对 Props 进行类型检查?
参考答案:
- TypeScript(推荐):在企业项目中最常用,编译时检查,支持复杂类型
- PropTypes:运行时检查,适合 JavaScript 项目,React 19 中函数组件已移除支持
- 默认值兜底:配合参数默认值,减少 undefined 导致的错误
面试题 10:React 19 对 Props 做了哪些改变?
参考答案:
- ref 成为普通 prop :函数组件可以直接从 props 中解构
ref,不再需要forwardRef - 移除函数组件的 defaultProps:推荐使用参数默认值
- 移除函数组件的 propTypes:推荐使用 TypeScript
- 自定义元素支持:props 可以更好地传递给 Web Components
十、总结与最佳实践清单
10.1 核心要点回顾
- Props 是 React 组件通信的基石,掌握它是学好 React 的第一步
- 单向数据流 + 只读性 是 Props 的灵魂,理解了这两点就理解了 Props 的设计哲学
- 子传父用回调函数,这是 React 的标准模式
- Prop Drilling 用组合模式解决,不要一上来就用 Context 或 Redux
- 性能优化靠 React.memo + useMemo + useCallback,但不要过度优化
10.2 企业级最佳实践清单
- 使用函数组件 + 解构赋值 + 参数默认值
- 使用 TypeScript 定义 Props 类型,必填项不加
? - 容器组件负责逻辑,展示组件只负责渲染
- 回调函数命名用
onXxx格式,如onClick、onSubmit - 避免在渲染中创建新对象和新函数作为 props
- 纯展示组件用
React.memo包裹 - 优先用组合模式解决 Prop Drilling,而不是 Context
- 不要在子组件中直接修改 props 或 props 中的对象属性
- 列表渲染必须加
key,且不要用 index 作为 key - 组件 Props 接口保持稳定,避免频繁变更
10.3 学习路径建议
- 入门阶段:掌握基础传递、解构、默认值、回调函数
- 进阶阶段:理解单向数据流、学习组合模式、Render Props
- 高级阶段:性能优化、TypeScript 类型体操、设计模式
- 架构阶段:组件设计原则、状态管理选型、跨团队组件库建设
Props 看似简单,但它是 React 整个组件化体系的根基。把 Props 用好了,你的 React 代码会更清晰、更易维护、性能更好。希望这篇文章能帮你彻底掌握 Props,在日常开发和面试中都能游刃有余。
转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。