888 字
4 分钟
React 组件通信

只有两个方向#

React 里组件之间的通信只有两个方向:数据往下传(props),事件往上报(回调函数)。所有距离上的做法都是这两条的组合。

父子:数据下行,事件上行#

function SearchInput({ value, onChange }) {
return (
<input
value={value}
onChange={e => onChange(e.target.value)}
placeholder="搜索"
/>
)
}
function SearchPage() {
const [query, setQuery] = useState('')
return (
<>
<SearchInput value={query} onChange={setQuery} />
<ResultList query={query} />
</>
)
}

SearchInput 自己不存状态,它拿到的 value 和 onChange 都由父组件提供。这一层抽象叫受控组件,输入框的一切都由上层决定,父组件因此能在同一处拿到关键词并传给结果列表。受控与非受控的取舍见 受控组件与非受控组件。

子组件永远不改接收到的 props。要改,就把「改成什么」通过回调交回去。

兄弟:把状态提到共同父组件#

两个兄弟组件要共享同一份数据时,不要把状态放在其中一方,那会让另一方必须去找它。把状态提到两者的共同父组件,再分别传下去:

function TemperatureInput({ label, value, onChange }) {
return (
<label>
{label}:
<input value={value} onChange={e => onChange(e.target.value)} />
</label>
)
}
function BoilingVerdict({ celsius }) {
const value = parseFloat(celsius)
return <p>{value >= 100 ? '水会沸腾' : '水不会沸腾'}</p>
}
function Calculator() {
const [celsius, setCelsius] = useState('')
return (
<>
<TemperatureInput label="摄氏度" value={celsius} onChange={setCelsius} />
<BoilingVerdict celsius={celsius} />
</>
)
}

这是官方所说的「把状态提上去」:Calculator 持有唯一的真值,两个子组件都只是显示与上报。

跨多层:先试组合,再上 Context#

数据要从最外层传到第四层时,中间几层都成了搬运工。两条路可以走。

先试组合:让中间层接收 JSX 作为 children,它就不必认识那些数据。

function Layout({ children }) {
return <div className="layout">{children}</div>
}
function App() {
const theme = 'dark'
return (
<Layout>
<Nav theme={theme} />
</Layout>
)
}

Nav 由 App 直接渲染,Layout 不必认识 theme。

组合解决不了时再用 Context。创建、提供、读取三步,以及「value 变化会让所有消费者重渲染」这类坑,写在 useContext 一篇里,这里不重复。

全局:先问清楚「全局」是不是真的#

跨模块共享的数据(登录态、主题、语言)才值得放进状态库或全局 Context。判断标准是读者能不能一眼看出谁依赖它。

自定义事件总线这类做法要避免。它绕开了 React 的数据流:组件之间不再有可见的依赖关系,一次渲染是谁触发的很难追;订阅与退订还得手工配对,漏一处就是内存泄漏。真需要订阅 React 之外的数据源(localStorage、浏览器 API、第三方 store),走 useSyncExternalStore,把订阅契约写在一个地方。

传下去的东西会决定要不要重渲染#

把每次渲染都新建的对象或函数传给被 memo 包起来的子组件,等于把优化击穿:

// 每次渲染都是新对象,子组件的 memo 拦不住
<Child config={{ mode: 'compact' }} />
// 放在组件外,或在需要时用 useMemo 固定引用
const config = { mode: 'compact' }
<Child config={config} />

先看有没有必要:子组件没包 memo,或者在简单场景里,直接传字面量更省事。要固定引用时再用 useMemo 与 useCallback。

选择顺序#

按距离从近到远选:props 与回调 → 提升状态 → 组合 → Context → 状态库。

越往后的方案,代价越在于「依赖关系变得不可见」。能用近的,就别用远的。

第 6 篇,共 39 篇本文属于系列

React 组件通信
https://www.choria.top/posts/props/
作者
乔瑞雅·克林威尔
发布于
2026-05-03
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 189 天

部分内容可能已过时