React 组件间传参好痛苦?试试这一套"优雅组合拳":Context 与 Refs 实战
阶段概述
这篇聊三个东西:Context API 怎么解决层层传参的噩梦、React 19 新出的 use API 用起来哪里比 useContext 顺手、以及 useRef 的两个实际用途------存跨渲染周期的数据,和直接操控 DOM 节点。
第一部分:跨组件共享 Context API
1. 属性钻孔 (Prop Drilling) 的架构痛点
React 是单向数据流,数据只能从父组件往下传。如果一个深层嵌套的组件需要顶层的数据,你得把这个数据作为 props,经过所有中间组件一层一层传下去------哪怕这些中间组件根本用不上它。
这就是 Prop Drilling,两个明显的问题:
- 改一个字段,链路上所有中间文件都要跟着改,重构起来很痛苦。
- 中间组件被迫接收跟自己没关系的数据,复用性基本废掉了。
2. React 19 对 Context 语法的大幅简化
Context API 的思路是在顶层"广播"数据,任意深度的后代组件都能直接订阅,不用经过中间层。
React 18 及之前的写法:
jsx
<UserContext.Provider value={user}>
<Header />
</UserContext.Provider>
React 19 的写法,.Provider 后缀直接去掉了:
jsx
<UserContext value={user}>
<Header />
</UserContext>
旧的 <Context.Provider> 写法在 React 19 里还能用,但属于向后兼容,后续主要版本会移除,官方也会提供迁移工具。
3. Context 配合 State 的独立 Provider 封装
真实项目里,通常把 Context 的创建、状态声明和业务方法单独封装在一个文件里,避免顶层 App.jsx 越来越臃肿:
jsx
// UserContext.jsx
import React, { createContext, useState } from 'react';
export const UserContext = createContext();
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = () => setUser({ name: 'Alex' });
const logout = () => setUser(null);
return (
<UserContext value={{ user, login, logout }}>
{children}
</UserContext>
);
};
在 App.jsx 里只需要用 <UserProvider> 包一下,里面所有组件都能直接消费 user,不用再层层传参了。
第二部分:React 19 全新 use API 消费 Context
1. use API 是什么
之前消费 Context 只能用 useContext,而且必须在组件顶层无条件调用。React 19 加了 use API,用法很简单:
javascript
import { use } from 'react';
const user = use(UserContext);
2. use 比 useContext 好在哪里
最大的区别:use 可以写在 if 语句或条件渲染里,useContext 不行。
标准的 React Hook 有一条铁规则------不能在条件分支或循环里调用,useContext 也不例外。use 打破了这个限制,这在实际业务里挺有用的:
jsx
export default function Avatar({ showProfile }) {
if (showProfile) {
// 在 if 里调用 use 没问题;换成 useContext 这里会直接报错崩溃
const { user } = use(UserContext);
return <p>Welcome, {user.name}</p>;
}
return <p>Guest</p>;
}
useContext 在 React 19 里还是支持的,写新代码的话官方更推荐用 use。
第三部分:Refs 与 useRef 逃生舱
1. useRef 的第一大用途:保存跨渲染周期的可变值
useRef 返回一个带 .current 属性的对象,在组件整个生命周期里保持同一个引用。关键点是:修改 .current 不会触发重新渲染。
用秒表来说明这个特性比较直观。实现计时器的启动和停止,需要保存 setInterval 返回的 intervalId:
- 用普通局部变量:组件一重渲染变量就重置了,
clearInterval找不到之前的 id,计时器停不掉。 - 用
useState保存:每次存 id 都会触发一次重渲染,多余的渲染什么也没做。 - 用
useRef:intervalRef.current = setInterval(...)存起来,停止时clearInterval(intervalRef.current),不触发渲染,干净。
2. useRef 的第二大用途:直接操纵 DOM 节点
在 JSX 元素上挂 ref 属性,React 渲染完成后会把真实 DOM 节点写进 current,然后你就能直接调 DOM API:
- 自动聚焦输入框:
inputRef.current.focus() - 滚动到视野内:
containerRef.current.scrollIntoView() - 视频播放控制:
videoRef.current.play()/.pause() - 测量元素尺寸:
elementRef.current.getBoundingClientRect()
有一条要注意:ref 只做读取或交互控制,不要用它绕过 React 去改 DOM 结构,也不要强行改 input 的 value。一旦绕开了虚拟 DOM,React 下次渲染时会把你改的东西直接覆盖掉,或者产生数据和视图不一致的 bug,很难排查。
写在最后
到这里,我们用三篇文章把 React 19 的基础到进阶核心概念都过了一遍。从最开始的 JSX 语法规则,到中途让人头疼的 State 异步快照,再到最后的 Context 跨组件通信和 Refs 逃生舱,这套组合拳基本覆盖了你日常开发 80% 的场景。
React 19 带来的 use API 简化了 Context 的消费,也给了我们更多编写条件逻辑的自由。技术概念聊完了,接下来就是把这些拼图拼在一起,去写实际的业务代码。多踩几次 State 不更新的坑,多写几个自定义 Provider,这些机制自然就变成你的肌肉记忆了。