单项数据流的“后门”
需要先理解react的单向数据流,才能理解“后门”的原理。
有时候我们需要命令式地去控制组件(“喂,子组件,立刻给我把输入框清空并聚焦!”)。
这需要三个搭档配合:
useRef:父组件手里的“遥控器”。forwardRef:连接线,让父组件的“遥控器”能连上子组件。useImperativeHandle:子组件里的“控制面板”,决定父组件能按哪些按钮。
代码实现
- 第一步:子组件使用
forwardRef和useImperativeHandle
子组件必须把自己包裹在 forwardRef 里,否则它接收不到父组件传来的 ref。
jsx
import React, { useRef, useImperativeHandle, forwardRef, useState } from 'react';
// 1. 使用 forwardRef 包裹组件,这样才能接收第二个参数 ref
const MyInput = forwardRef((props, ref) => {
const inputRef = useRef();
const [val, setVal] = useState('');
// 2. 使用 useImperativeHandle 定义暴露给父组件的方法
// useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle(ref, () => ({
// 方法 A: 聚焦
focus: () => {
inputRef.current.focus();
},
// 方法 B: 清空并聚焦 (组合拳)
clearAndFocus: () => {
setVal(''); // 清空状态
inputRef.current.focus(); // 聚焦 DOM
},
// 方法 C: 一个完全自定义的动作
sayHello: () => {
alert("来自子组件的问候!");
}
}));
return (
<input
ref={inputRef} // 绑定内部真实的 DOM
value={val}
onChange={e => setVal(e.target.value)}
placeholder="写点什么..."
style={{ padding: '10px', fontSize: '16px' }}
/>
);
});- 父组件创建 Ref 并调用
jsx
function App() {
// 1. 创建一个 ref (也就是遥控器)
const childRef = useRef(null);
const handleClick = () => {
// 2. 通过 ref.current 直接调用子组件暴露的方法
// 注意:这里只能调用 useImperativeHandle 里定义的那几个方法
if (childRef.current) {
childRef.current.clearAndFocus();
}
};
return (
<div style={{ padding: '20px' }}>
<h1>父组件控制台</h1>
<button onClick={handleClick}>
清空并聚焦子组件
</button>
<button onClick={() => childRef.current.sayHello()}>
让子组件打招呼
</button>
<hr />
{/* 3. 把 ref 传给子组件 */}
<MyInput ref={childRef} />
</div>
);
}
export default App;原理解析
你可以把这个过程想象成 “定制 API”
- 没有
useImperativeHandle时: 如果父组件把 ref 传给一个原生的<input ref={ref} />,父组件拿到的就是整个 DOM 节点。父组件可以随心所欲地操作 DOM,甚至做一些破坏性的事情(比如dom.remove()),这很不安全。 - 有了
useImperativeHandle时: 子组件说:“父组件你别直接碰我的 DOM。我给你提供一个手柄 (Handle),上面只有focus和clear两个按钮。你只能按这两个按钮,别的不能动。”- 安全性:隐藏了内部实现细节(比如内部用了什么 DOM 结构)。
- 灵活性:可以将多个操作组合成一个方法(比如 clearAndFocus)。
什么时候用🚀?
React 官方文档:
Ref 是逃生舱 🚀 (Escape Hatch),不是首选方案。
| 场景 | 推荐做法 | 为什么? |
|---|---|---|
| 想要修改子组件的内容 | ✅Props | 符合 React 单向数据流,易于维护。 |
| 想要控制显示/隐藏 | ✅Props | (State) isOpen={true} 比调用 open() 方法更清晰。 |
| 媒体播放 (Play/Pause) | 🚀 Ref | 视频播放是时间相关的动作,不是状态。 |
| 焦点管理 (Focus) | 🚀 Ref | 聚焦是一个瞬时动作。 |
| 强制滚动 (Scroll to) | 🚀 Ref | 滚动位置通常不存放在 State 里。 |
| 集成第三方非 React 库 | 🚀 Ref | 比如集成 D3.js 或 Google Maps,必须操作 真实DOM。 |