本节摘要:props 是父组件传进来的只读数据,state 是组件自己管的可变数据。二者配合构成 React 的"单向数据流":数据从根往下流,更新靠各组件用 setter 自下而上报回。本节把这套心智讲透,它是 React 里最重的一课。
本节阅读目标
阅读完本节,你应当能够:
props(properties):父组件通过 JSX 属性传进来的数据,组件内部只读,不该擅自改它。
state:组件内部自己创建的、可变的动态数据。要变,就调用它的更新函数,让组件重渲染。
一句话区分边界:props 是别人给的约定,state 是我自己许的变化。
function ItemCounter({ initial }) { const [count, setCount] = useState(initial); return <button onClick={() => setCount(count + 1)}>次数:{count}</button>; }
这里 initial 是 props(父级传进来),count / setCount 是 state(组件自管)。改 count 的唯一途径是 setCount,这正是 React"显式更新"路线的体现(回顾 2.3 节)。
单向数据流是 React 最核心的构图:
父组件的 data │ 通过 props 向下传给子组件 ▼ 子组件读 props(只读) │ 内部用 state 自管 ▼ 需要向上反馈时 → 调用父级传来的回调事件
好处是可预测:数据从哪来、往哪去一目了然,排查问题只用沿着树向上找。坏处是:兄弟要共享数据得麻烦一次,见下一节。
当两个兄弟组件需要看同一份数据时,正确做法是把这份 state"提升"到它们共同的父组件,再通过 props 下发,同时下发一个能改它的回调:
function App() { const [theme, setTheme] = useState('light'); const toggle = () => setTheme(theme === 'light' ? 'dark' : 'light'); return ( <div> <ThemeSwitch theme={theme} onToggle={toggle} /> <Content theme={theme} /> </div> ); }
ThemeSwitch 与 Content 都不自管 theme,theme 归 App 管,分别通过 props 拿值和事件。数据一旦一致,两个儿子看到的永远是同一份值。这就是比稿现场反复强调的"接口在上游,数据集中在能覆盖所有使用者的那层"。
围绕 props 与 state 有两个新手最常踩的直觉。第一是"直接在子组件里改 props"。props 是父级的所有物,子组件去改它会绕过"单向数据流",下一秒父组件或其他兄弟拿到什么值就完全不可控了。规则是:要改,就把"怎么改"作为回调传给子组件的接口,让父级自己决定怎么变。这跟前面反复说的"事件只是上报、处置交给上层"完全同源。
第二个陷阱是把"来自 props 的数据"不经处理就拿来当 state 用。比如用 useState(props.items) 初始化一份副本,本意是想"本地再加工",却往往在 props 变化时忘记同步这份副本,导致界面和父级数据脱节。若你真的需要一个从 props 派生的值,更稳的做法是别存副本,而是每次渲染都根据 props 算出来;只有确实只有本组件用、又要长期保留的,才存进 state。

状态提升够用就别动其他方案。只有大量组件要共享同一份深层数据、或数据跨层传递链太长时,才轮到 context 或状态库(第 6.1 节详谈)。原则是一级级来,别拿大炮打蚊子。
把"提升"二字落到一段能跑的代码上,比记一百遍口诀来得实在。假设你要做一个"购物车":顶部有计数徽标,列表里每个商品的"加入购物车"按钮都要让徽标 +1。如果每个商品自管一份数量,页面上就有 N 份互不认识的计数——这就是典型的"兄弟要共享一份数据"。
解法是把"总量"提到它们共同的父级 Cart:
function Cart() { const [total, setTotal] = useState(0); // 共享数据提到父级 return ( <div> <Badge count={total} /> // 徽标只读 data <List onAdd={() => setTotal((t) => t + 1)} /> // 列表只报"加了" </div> ); }
看这段的接口:List 根本不知道 total 是多少,它只需要有一个 onAdd 回调可调;Badge 也不管按钮在哪,它只管把收到的 count 显示出来。真正握着 total、并且决定"加 1 还是加 2"的,只有 Cart 自己。这正是一条干净的"单向数据流"剖面:数据往上集中在唯一负责人身上,界面各取所需、各报其事。
这套模式你会在第 5 章的组件通信里反复遇到——那时它有个更正式的名字叫"状态提升"与"受控组件"。现在先记住:当两个组件发现自己都要访问同一份数据时,先把这份数据搬上它们共同的那层。这一步往往就能化解大多数"兄弟不同步"的焦虑,也把思考方向收拢到"数据到底该归谁管"这一个问题上,而不是在组件之间偷偷传引用。
props 与 state 的样子定了,接下来看函数组件那套"开挂"工具——Hooks,如何把状态、副作用、缓存统一进函数组件的能力体系。