一句话定义
Props 是父组件传给子组件的只读输入,像函数参数;子组件要「回报」只能调用父传下来的回调函数——这就是单向数据流。
为什么重要
数据流向清晰,bug 才可定位:任何界面异常,都能沿 props 链一路向上问到源头。单向数据流是 React 架构可维护性的第一支柱,也是 Flux/Redux 等状态库的思想源头(kp-021)。
前置知识
- kp-003 组件与树。
- 解构赋值、默认参数。
核心概念
- props 只读:子组件不得改写 props(包括修改其中的对象/数组)。
- children:一种特殊 prop,承载 JSX 子节点(详见 kp-013)。
- 回调下行:父把函数当 prop 传下去,子在事件里调用它来「改变父的状态」。
- Props 下钻(Prop Drilling):数据穿过多个不关心它的中间层——过长时是改用 Context(kp-012)的信号。
原理 / 机制
组件签名的本质是函数签名:
jsx// 两种等价写法
function Greet({ name, onFollow }) { ... } // 推荐解构
function Greet(props) { const { name } = props; } // 先拿对象再解构
只读约束为什么存在:
jsxfunction ProductList({ items }) {
items.sort((a, b) => a.price - b.price); // ❌ 原地修改父的数组
return <ul>...</ul>;
}
sort 原地排序意味着:即使父组件没变状态,数组内容也已被偷偷改写;并发渲染下多次读取还会得到不同结果。正确做法是 items = [...items].sort(...) 或 items.toSorted(...)——先复制再改。
回调下行实现「子改父状态」:
jsxfunction App() {
const [liked, setLiked] = useState(false);
return <LikeButton liked={liked} onToggle={() => setLiked(v => !v)} />;
}
function LikeButton({ liked, onToggle }) {
return (
<button onClick={onToggle} aria-pressed={liked}>
{liked ? '❤️ 已赞' : '🤍 点赞'}
</button>
);
}
数据(liked)向下,意图(点击事件)向上,App 是唯一知道「状态怎么变」的地方。
直观类比
props 像快递单:内容写死、地址写死,收件人只有使用权没有修改权;想退换货要走「联系发货方」的流程(回调)。单向数据流像瀑布:水只能往下流,你想让下面的东西上去,得靠抽水机——而且抽水机必须安装在上面(回调定义在父组件)。
实例 / 案例
状态提升(Lifting State Up):两个兄弟组件共享同一状态时,把状态提到共同父组件:
jsxfunction App() {
const [keyword, setKeyword] = useState('');
return (
<>
<SearchBox value={keyword} onChange={setKeyword} />
<ResultList query={keyword} />
</>
);
}
SearchBox 是「受控输入」(kp-011 的主题):它自己不持有 keyword,只负责展示与上报。
常见误区
- 在子组件里改 props 对象/数组:见上方 sort 例子;应复制后操作。
- 把回调写成调用结果:
onClick={setOpen(true)}立即执行导致死循环;应为onClick={() => setOpen(true)}。 - 无脑 prop drilling 到底:中间 5 层都只为转发一个 theme 属性,应改用 Context 或组合(kp-012 / kp-013)。
- 传布尔开关爆炸:
<Modal open closable dark size="lg" ...>十几个 flag,说明该改用 children 组合来定制内容。
自测题
- 为什么「修改 props 里的数组」比「修改 state 里的数组」更隐蔽、更危险?
- 实现:
<Tabs tabs={[...]} active={i} onSelect={fn} />,说明哪几个数据向下、哪个意图向上。 - prop drilling 出现的最早信号是什么?给出两条升级路径及各自的适用场景。
与其他知识点的关系
- 向前:kp-003 的树结构决定了 props 只能沿边传递。
- 向后:kp-005 是「数据源头」如何持有与更新;kp-012 解决 drilling;kp-013 用 children 替代一串配置 props;kp-025 的 Actions 把「提交表单」这类上行意图进一步标准化。
延伸阅读
- Passing Props to a Component:<https://react.dev/learn/passing-props-to-a-component>
- Sharing State Between Components:<https://react.dev/learn/sharing-state-between-components>