React组件从外部接收数据的入口就是props,它是只读的,相当于函数声明中的形参。父组件在JSX里给子组件元素添加属性,React会把属性打包成一个对象传给子组件。子组件无论是函数组件还是类组件,都能在内部拿到这个对象。基础传递方式如下:

// 父组件
function Parent() {
const user = { name: 'Alice', age: 28 };
return (
<div>
<Child name={user.name} age={user.age} />
</div>
);
}
// 子组件(函数组件)
function Child(props) {
return (
<p>{props.name} 今年 {props.age} 岁</p>
);
}
上面代码中,Parent把name和age两个属性传给Child,Child通过props.name和props.age访问。函数组件也可以使用解构赋值让代码更简洁:function Child({ name, age }) { ... }。需要注意的是,props是只读的,如果子组件尝试给props.name赋新值,React不会响应,严格模式下还会直接报错。想让数据变化,必须由父组件修改自己的state,再把新值通过props传下去,这就是单向数据流的核心。
类组件获取props的方式略有不同。类组件通过this.props访问,不需要额外的构造函数参数,但如果要使用super(props)可以在构造函数中访问。示例:
class Child extends React.Component {
render() {
return <p>{this.props.name}</p>;
}
}
子组件如何向父组件传递数据:回调函数
props只能从父流向子,但子组件经常需要把用户操作、表单数据或内部状态告诉父组件。React没有内置的子传父指令,常规做法是父组件先定义一个回调函数,把这个函数作为props传给子组件;子组件在合适时机调用这个函数,并传入参数,父组件就能在回调中收到数据。这种模式本质上是把父组件的函数向下传递,让子组件触发父组件的逻辑。
举个例子,一个表单的子组件需要把输入内容同步给父组件。父组件维护inputValue状态,把setInputValue或自定义函数handleChange传给子组件。子组件的<input>元素在onChange事件中调用props.onChange,把事件对象或值传回。代码实现:
function Parent() {
const [value, setValue] = React.useState('');
const handleChange = (newValue) => {
setValue(newValue);
};
return (
<div>
<InputField value={value} onChange={handleChange} />
<p>当前输入:{value}</p>
</div>
);
}
function InputField({ value, onChange }) {
return (
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
这个例子中,InputField没有自己的内部状态,它完全受父组件控制,通常称为受控组件。回调函数名约定为onSomething,例如onChange、onSubmit、onDelete,这样代码可读性更强。如果子组件内部状态复杂,也可以保留局部state,只在提交等关键时刻把数据一次性传回父组件。回调函数在每次渲染时重新创建,可能影响React.memo优化,此时可以使用useCallback包裹回调。
需要强调的是,回调函数不是严格意义上的props传递数据,而是传递函数引用。数据仍然通过参数从子组件流向父组件,但触发动作由父组件控制。这种方式可以处理兄弟组件通信:两个兄弟组件需要交互时,可以把共享状态提升到它们最近的共同父组件,再由父组件通过props和回调分发数据。
跨层级传递:Context与组合模式
当组件层级较深时,如果一层层手动转发props,会出现“props drilling”问题。中间组件可能并不关心某些数据,但为了让底层组件拿到,必须逐层传递。React提供了Context API来解决跨层级共享数据。Context允许创建一个数据源,在顶层组件通过Provider提供值,任意深度的子组件使用useContext或Consumer直接订阅,不需要经过中间层。
使用Context的典型场景包括当前登录用户、主题、语言偏好等全局信息。先调用React.createContext创建上下文对象,顶层用Provider包裹,子组件调用useContext获取。代码示例:
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
Context虽然方便,但不能滥用。它的值一旦变化,所有订阅该Context的后代组件都会重新渲染,即使某些组件只用到了部分数据。可以通过拆分多个Context、使用useMemo缓存value、或第三方状态管理库优化。另外,Context不具备响应式更新后的局部精细控制能力,对于频繁变化的复杂状态,仍然建议使用Redux或Zustand等方案。
组合模式是另一种避免props drilling的手段。核心思想是利用props.children把JSX片段直接传递。父组件不关心子组件具体要什么数据,只提供布局或容器。数据由真正的使用方携带。例如:
function Layout({ sidebar, content }) {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
);
}
function App() {
const user = { name: 'Bob' };
return (
<Layout
sidebar={<Sidebar user={user} />}
content={<Content user={user} />}
/>
);
}
这里Layout不需要知道user的存在,它只负责渲染插槽。Sidebar和Content在父组件中创建,直接拿到user数据。组合模式让组件职责更单一,代码也更灵活。在实战中,优先考虑组合,只有确实需要全局共享时才引入Context。
增强props健壮性:默认值与类型检查
由于JavaScript是动态类型语言,props传错类型或漏传时,往往在运行时才暴露问题。React提供了defaultProps和prop-types库来缓解这个问题。函数组件可以直接给参数设置默认值,也可以使用defaultProps静态属性;类组件则通过Component.defaultProps定义。当父组件未传某个prop时,默认值生效,避免子组件内部出现undefined导致异常。
例如给按钮组件设置默认文案和类型:
function Button({ text = '确定', type = 'primary' }) {
return <button className={`btn-${type}`}>{text}</button>;
}
这里直接在解构时给默认值,是函数组件最简洁的写法。类组件可使用Button.defaultProps。类型检查使用prop-types库,运行时验证传入props的类型,警告不匹配的情况。用法:
import PropTypes from 'prop-types';
function User({ name, age, hobbies }) {
return (
<ul>
<li>{name}</li>
<li>{age}</li>
<li>{hobbies.join(', ')}</li>
</ul>
);
}
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
hobbies: PropTypes.arrayOf(PropTypes.string)
};
User.defaultProps = {
age: 18,
hobbies: []
};
这样即使父组件遗漏了hobbies,子组件也能安全地调用join。需要注意的是,prop-types仅在开发环境警告,不会阻止代码运行,生产环境建议去掉这些检查以减小体积。TypeScript用户可以通过接口定义props类型,在编译期就发现错误,是目前大型项目的推荐做法。
render props模式:复用渲染逻辑
render props是一种通过props传递函数来控制组件渲染内容的技术。某些组件只提供数据或状态逻辑,不决定具体UI,把渲染权交给使用方。使用方式:组件接收一个名为render的prop(也可以是任何函数prop),在内部调用this.props.render(data)或props.render(data),返回JSX。这种模式在React Router、Formik等库早期版本中广泛使用。
经典例子是鼠标位置跟踪组件。该组件维护鼠标坐标state,但坐标如何展示由调用方决定:
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<p>鼠标位置:{x}, {y}</p>
)}
/>
);
}
render props的优点是逻辑复用非常灵活,不依赖继承。缺点是嵌套多层时会出现回调地狱,且每次渲染都会创建新函数,可能造成不必要的子组件重渲染。现在React官方推荐使用自定义Hook来替代大部分render props场景。自定义Hook同样可以封装状态逻辑,但不会增加组件层级。例如将鼠标跟踪封装为useMousePosition,在组件中直接调用。
尽管render props不再是主流,但理解这个模式有助于读懂旧代码和第三方库源码。它本质上是把函数作为props传递,属于props高级用法。结合props.children也可以实现类似效果,将函数作为children传入,内部用props.children(data)调用,代码更自然一些。
总结
React传递props的方法可以归纳为几个层次:父传子直接通过JSX属性;子传父借助回调函数;跨层级用Context或组合模式;增强健壮性用默认值和类型检查;复用渲染逻辑可以用render props或自定义Hook。实际开发中应该根据组件层级和数据变化频率选择合适方案。理解props单向数据流和只读特性,是掌握React组件通信的关键。建议从简单父子组件开始练习,逐步引入Context和Hook,形成清晰的组件边界。
React props组件通信props传递修改时间:2026-10-01 07:11:54