起源背景
首先,类组件可以这么加 ref,但是!函数组件不能加ref啊!!
虽然可以用ref转发—React.forwardRef(高阶组件,可以传一个FC,返回的是新的CC—类组件),
function Test(props, ref){
return <h1 ref={ref}> Test Component </h1>
}
const TestWrapper = React.forwardRef(Test);
但是它仍然不能调用method方法啊,因为ref在上h1上,所以才衍生出了ImperativeHandle HOOK
useImperativeHandle 函数
- 第一个参数就是 ref
- 第二个参数是一个函数
- 若不给依赖项,则每次运行函数组件都会调用该方法
- 如果使用了依赖项,则第一次调用后,会进行缓存,只有依赖项发生变化才会重新调用
- 返回值就是 ref.current 的值
- 第三个参数就是依赖项
所以最终可以写成如下方式,解决上述问题:
直接转发 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 的转发。
import React, {
useState,
useRef,
useImperativeHandle,
useCallback
} from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const [ fresh, setFresh ] = useState(false)
const attRef = useRef(0);
useImperativeHandle(ref, () => ({
attRef,
fresh
}), [ fresh ]);
const handleClick = useCallback(() => {
attRef.current++;
}, []);
return (
<div>
{attRef.current}
<button onClick={handleClick}>Fancy</button>
<button onClick={() => setFresh(!fresh)}>刷新</button>
</div>
)
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button
onClick={() => console.log(fancyInputRef.current)}
>父组件访问子组件的实例属性</button>
</div>
)
}
ReactDOM.render(<App />, root);
上面的案例相对于官方的例子意图更明显一些,通过 useImperativeHandle 将子组件的实例属性输出到父组件,而子组件内部通过 ref 更改 current 对象后,组件不会重新渲染,需要改变 useState 设置的状态才能更改。