主题
forwardRef 和 useImperativeHandle
介绍
forwardRef是一个高阶组件,用于将 ref 转发到子组件,使得父组件可以直接访问子组件的 DOM 元素或实例方法。useImperativeHandle是一个 React Hook,用于在函数组件中自定义实例值的暴露。它通常与forwardRef一起使用,以便父组件可以访问子组件的某些方法或属性。
快速入门
语法
forwardRef
js
forwardRef((props, ref) => {
// 组件逻辑
})参数:
props:组件的属性。ref:一个 ref 对象,用于接收父组件传递的 ref。
useImperativeHandle
js
useImperativeHandle(
ref, // 父组件传递的 ref
() => ({
// 返回暴露的接口
focus: () => {},
clear: () => {},
}),
[], // 依赖数组
)参数:
ref:一个 React ref 对象,用于将暴露的实例值传递给父组件。createHandle:一个函数,返回一个对象或方法,这些将被暴露给父组件。deps:一个可选的依赖项数组,当其中的值发生变化时,createHandle将被重新调用。
示例
jsx
import React, { useImperativeHandle, forwardRef, useRef } from 'react'
function ChildComponent(props, ref) {
const localRef = useRef()
useImperativeHandle(ref, () => ({
focus: () => {
localRef.current.focus()
},
getValue: () => {
return localRef.current.value
},
}))
return <input ref={localRef} type="text" />
}
export default forwardRef(ChildComponent)jsx
import React, { useRef } from 'react'
import ChildComponent from './ChildComponent'
function ParentComponent() {
const childRef = useRef()
const handleFocus = () => {
childRef.current.focus()
}
const handleGetValue = () => {
alert(childRef.current.getValue())
}
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleGetValue}>Get Input Value</button>
</div>
)
}
export default ParentComponent实现父子组件间的“精准通信”
forwardRef 的作用
在 React 的世界里,数据通常是“自上而下”的单向流动的。
- 父组件通过 props 向子组件传递数据。
- 子组件通过 回调函数 向父组件传递数据。
这种模式保证了应用的 稳定性 和 可预测性。
但是 …… 有些场景下需要“逆向”操作,父组件需要主动触发子组件的某个方法:
jsx
parentComponent.triggerChildFocus()
parentComponent.clearChildInput()
parentComponent.scrollToChildToTop()
// ...函数组件的“无实例”问题
jsx
function MyInput(props) {
return <input {...props} />
}
function App() {
const inputRef = useRef()
// ❌ 报错:Function components cannot be given refs.
return <MyInput ref={inputRef} />
}函数组件默认是无法接收 ref 的!为什么?
- 类组件 有实例,可以直接附加 ref
- 函数组件 只是函数,没有实例的概念
- 原生 DOM 元素 可以直接接收 ref
forwardRef(建立通道):
- 解决问题:ref 无法传递给函数组件。
- 核心职责:打通父子组件的 ref 连接。
- 工作方式:将 ref 从父组件转发到子组件内部。
jsx
function App() {
const inputRef = useRef(null)
const handleFocus = () => {
inputRef.current?.focus()
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleFocus}>聚焦输入框</button>
</>
)
}jsx
const MyInput = forwardRef((props, ref) => {
// ref 参数就是父组件传递过来的 ref
return <input ref={ref} {...props} />
})forwardRef 带来的问题
虽然 forwardRef 解决了 ref 转发的问题,但也同时造成了“过度暴露”的风险:
jsx
// 父组件可以任意修改
inputRef.current.value = '恶意篡改'
inputRef.current.style.display = 'none'这样破坏了封装性,合理的设计应该是:
- 子组件像黑盒一样工作
- 父组件不应该关心内部的 DOM 结构
- 接口应该是明确且有限的
useImperativeHandle 的作用
useImperativeHandle 可以让我们自定义 ref.current 的值。
useImperativeHandle(定义接口):
- 解决问题:避免过度暴露内部实现。
- 核心职责:精确控制暴露的 API。
- 工作方式:自定义
ref.current的值。
必须与 forwardRef 配合使用
jsx
const MyComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
// 只暴露 focus 和 clear 方法
focus: () => { ... },
clear: () => { ... },
}))
return <input ... />
})