引入:如何调用函数式组件内部的方法
对于 React 中需要强制修改子组件的情况,React 提供了 Refs 这种解决办法,使得我们可以操作底层 DOM 元素或者自定的 class 组件实例。除此之外,文档(v17.0.1)对函数式组件另有描述:
不能在函数式组件上使用ref属性,因为他们没有实例
。
在函数式组件和 Hooks 大面积普及的现在,这个特性没有完全对标 class 组件,令人疑惑。不过经过一阵探索和请教,发现确实是有对应的解决方案的:
useImperativeHandle
结合 React.forwardRef , useImperativeHandle 文档 应该就能明白是如何使用的。
简而言之就是可以在函数式组件上使用 ref,通过useImperativeHandle
这个hook
可以指定暴露给父组件的值和函数。
案例:
修改子组件Counter
中的值, 达到重置count
的目的:
![](https://img.imgdb.cn/item/6007946f3ffa7d37b335f942.gif)
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
方法。
![](https://img.imgdb.cn/item/600799b23ffa7d37b3388dc4.gif)
到这里,实际上已经达到了和class
中ref
对等的效果。通过给函数式组件设置 ref 并调用组件的方法是可行的,useImperativeHandle
除了添加方法,也可以指定值暴露出去。
函数式组件的Ref是什么
将 ref 设置到 HTML 元素上,获取的是对应的DOM元素,如span:
![](https://img.imgdb.cn/item/6007a2d13ffa7d37b33d3d83.jpg)
设置到 class 组件上,获取的是 class 组件实例:
![](https://img.imgdb.cn/item/6007a34b3ffa7d37b33d75e3.jpg)
设置到函数式组件上的时候,获取的是一个包含可变值或函数的对象,如上例的 Counter 组件:
![](https://img.imgdb.cn/item/6007a4c23ffa7d37b33e5e7c.jpg)
React.createRef
和 useRef
都是创建了一个包含current
属性的对象,绑定ref
时,对应的属性和函数都在current
对应的对象中。
查看对应的TypeScript
类型,React.createRef
创建的是React.RefObject
类型,是只读的。
![](https://img.imgdb.cn/item/6007a6fc3ffa7d37b33f41de.jpg)
而useRef
创建的是React.MutableRefObject
,是可读写的。可以保存任何可变的值,使用方式类似于class
组件的this
实例变量。(又是和class
组件对标的一个点)
文档描述 useRef 为可以在其
.current
属性中保存一个可变值的“盒子”。
![](https://img.imgdb.cn/item/6007a75c3ffa7d37b33f8c60.jpg)
所以实际上应该是,对函数式组件可设置 ref,且设置的 ref 是一个可变对象,存放组件的变量,也能通过useImperativeHandle
访问函数式组件的方法。 但是并不能像将 ref 设置到 class 组件和 DOM 元素上那样获取到对应的实例。