标准答案
- 默认情况下,函数组件不能直接接收 ref 指向内部 DOM,需要用 forwardRef 显式转发。
- forwardRef 适合封装输入框、按钮、滚动容器等需要让外部聚焦、测量或滚动的组件。
- useImperativeHandle 可以自定义 ref 暴露的对象,只暴露 focus、reset、validate、open 等有限方法。
- 不要把整个内部 DOM 或内部状态都暴露出去,否则组件封装边界会被破坏。
- 大多数数据流仍应通过 props 和事件回调完成,ref 只用于少量命令式逃生口。
题目解析
这道题考的是封装边界。ref 是命令式能力,应该少而明确。
组件库常见做法是暴露 focus、blur、scrollTo、validate 这类稳定方法,而不是让业务随意操作内部 DOM 结构。
如果父组件需要频繁通过 ref 控制子组件状态,通常说明组件受控 API 设计不足。
代码示例
下面的输入框只向外暴露 focus 和 reset,而不是暴露完整内部实现。
TSX
type InputHandle = {
focus: () => void
reset: () => void
}
const SearchInput = forwardRef<InputHandle>(function SearchInput(_, ref) {
const inputRef = useRef<HTMLInputElement>(null)
useImperativeHandle(ref, () => ({
focus() {
inputRef.current?.focus()
},
reset() {
if (inputRef.current) {
inputRef.current.value = ''
}
}
}), [])
return <input ref={inputRef} />
})常见误区
- 为了方便父组件操作,把子组件内部 DOM 和状态全部暴露出去。
- 能用 props 控制的状态也用 ref 命令式修改,导致数据流难追踪。
- forwardRef 后没有定义清晰的 ref 类型,调用方不知道能用哪些方法。