React 学习站
React›基础›入门

Props 与单向数据流

入门基础

一句话定义

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 组合来定制内容。

自测题

  1. 为什么「修改 props 里的数组」比「修改 state 里的数组」更隐蔽、更危险?
  2. 实现:<Tabs tabs={[...]} active={i} onSelect={fn} />,说明哪几个数据向下、哪个意图向上。
  3. 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>