函数式组件 ref 解决方案

Published on

对于 React 中需要强制修改子组件的情况,React 提供了 Refs 这种解决办法,使得我们可以操作底层 DOM 元素或者自定的 class 组件实例。除此之外,文档(v17.0.1)对函数式组件另有描述:

不能在函数式组件上使用ref属性,因为他们没有实例

在函数式组件和 Hooks 大面积普及的现在,这个特性没有完全对标 class 组件,令人难以置信。经过一阵探索,发现确实是有对应的解决方案的:

useImperativeHandle

结合 React.forward , useImperativeHandle 文档  应该就能明白是如何使用的。

简而言之就是可以在函数式组件上使用 ref,通过useImperativeHandle这个hook可以指定暴露给父组件的值和函数。

案例:

修改子组件Counter中的值, 达到重置count的目的:

export default function App() {
  return (
    <div>
      <button>reset</button>
      <Counter />
    </div>
  )
}
/** -------------------------------------- */
function Counter() {
  const [count, setCount] = useState(0)
  function increment() {
    setCount(count + 1)
  }
  return (
    <div>
      <hr />
      <span>{count}</span>
      <button onClick={increment}>+1</button>
    </div>
  )
}

对于这个案例,将count这个 state 往上提一层到 App 组件中是比较合适的,但是在这里重点讨论操作子组件

使用useImperativeHandle,修改代码:

export default function App() {
  const counterRef = useRef()
  function reset() {
    counterRef.current?.resetCount()
  }
  return (
    <div style={{ padding: 10 }}>
      <button onClick={reset}>reset</button>
      <MyCounter ref={counterRef} />
    </div>
  )
}
/** -------------------------------------- */
function Counter(props, ref) {
  const [count, setCount] = useState(0)
  useImperativeHandle(ref, () => ({
    resetCount: resetCount,
  }))
  function resetCount() {
    setCount(0)
  }
  function increment() {
    setCount(count + 1)
  }
  return (
    <div>
      <hr />
      <span>{count}</span>
      <button onClick={increment}>+1</button>
    </div>
  )
}
const MyCounter = React.forwardRef(Counter)

重点是useImperativeHandle中定义了resetCount,以及使用React.forward获取 ref,在App组件中为MyCounter中定义ref属性,然后就可以在外部父组件中使用通过ref调用子组件的resetCount方法。

到这里,实际上已经达到了和classref对等的效果。

函数式组件的 Ref 是什么

将 ref 设置到 HTMl 元素上,获取的是对应的 DOM 元素,如 span:

设置到 class 组件上,获取的是 class 组件实例:

设置到函数式组件上的时候,获取的是一个包含可变值或函数的对象,如上例的 Counter 组件:

React.createRefuseRef 都是创建了一个包含current属性的对象,绑定ref时,对应的属性和函数都在current对应的对象中。

查看对应的TypeScript类型,React.createRef创建的是React.RefObject类型,是只读的。

useRef创建的是React.MutableRefObject,是可读写的。可以保存任何可变的值,使用方式类似于class组件的this实例变量。(又是和class组件对标的一个点)

文档描述 useRef 为可以在其.current属性中保存一个可变值的“盒子”。