中文:紧急的处理

起源背景

image.png
首先,类组件可以这么加 ref,但是!函数组件不能加ref啊!!
虽然可以用ref转发—React.forwardRef(高阶组件,可以传一个FC,返回的是新的CC—类组件),

  1. function Test(props, ref){
  2. return <h1 ref={ref}> Test Component </h1>
  3. }
  4. const TestWrapper = React.forwardRef(Test);

但是它仍然不能调用method方法啊,因为ref在上h1上,所以才衍生出了ImperativeHandle HOOK

useImperativeHandle 函数

  1. 第一个参数就是 ref
  2. 第二个参数是一个函数
    1. 若不给依赖项,则每次运行函数组件都会调用该方法
    2. 如果使用了依赖项,则第一次调用后,会进行缓存,只有依赖项发生变化才会重新调用
    3. 返回值就是 ref.current 的值
  3. 第三个参数就是依赖项

所以最终可以写成如下方式,解决上述问题:
image.png
直接转发 ref 是将 React.forwardRef 中函数上的 ref 参数直接应用在了返回元素的 ref 属性上,其实父、子组件引用的是同一个 ref 的 current 对象,官方不建议使用这样的 ref 透传,而使用 useImperativeHandle 后,可以让父、子组件分别有自己的 ref,通过 React.forwardRef 将父组件的 ref 透传过来,通过 useImperativeHandle 方法来自定义开放给父组件的 current。

useImperativeHandle 的第一个参数是 ref(一般是父组件传下来的),第二个参数是一个函数,返回值是一个对象,即这个 ref 的 current 对象,这样可以像上面的案例一样,通过自定义父组件的 ref 来使用子组件 ref 的某些方法。

useImperativeHandle 和 React.forwardRef 必须是配合使用的,这也是为什么在开头要介绍 ref 的转发。

  1. import React, {
  2. useState,
  3. useRef,
  4. useImperativeHandle,
  5. useCallback
  6. } from 'react';
  7. import ReactDOM from 'react-dom';
  8. const FancyInput = React.forwardRef((props, ref) => {
  9. const [ fresh, setFresh ] = useState(false)
  10. const attRef = useRef(0);
  11. useImperativeHandle(ref, () => ({
  12. attRef,
  13. fresh
  14. }), [ fresh ]);
  15. const handleClick = useCallback(() => {
  16. attRef.current++;
  17. }, []);
  18. return (
  19. <div>
  20. {attRef.current}
  21. <button onClick={handleClick}>Fancy</button>
  22. <button onClick={() => setFresh(!fresh)}>刷新</button>
  23. </div>
  24. )
  25. });
  26. const App = props => {
  27. const fancyInputRef = useRef();
  28. return (
  29. <div>
  30. <FancyInput ref={fancyInputRef} />
  31. <button
  32. onClick={() => console.log(fancyInputRef.current)}
  33. >父组件访问子组件的实例属性</button>
  34. </div>
  35. )
  36. }
  37. ReactDOM.render(<App />, root);

上面的案例相对于官方的例子意图更明显一些,通过 useImperativeHandle 将子组件的实例属性输出到父组件,而子组件内部通过 ref 更改 current 对象后,组件不会重新渲染,需要改变 useState 设置的状态才能更改。