标准答案

  1. 默认情况下,函数组件不能直接接收 ref 指向内部 DOM,需要用 forwardRef 显式转发。
  2. forwardRef 适合封装输入框、按钮、滚动容器等需要让外部聚焦、测量或滚动的组件。
  3. useImperativeHandle 可以自定义 ref 暴露的对象,只暴露 focus、reset、validate、open 等有限方法。
  4. 不要把整个内部 DOM 或内部状态都暴露出去,否则组件封装边界会被破坏。
  5. 大多数数据流仍应通过 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 类型,调用方不知道能用哪些方法。

作者信息