在一个父组件中,我们想要获取到其中的节点元素或者子组件实例,从而直接调用其上面的方法。Class 类组件和函数组件是两种不同的写法。
在 React 的 Class 组件中,我们通过 createRef
创建 ref
class Parent extends React.Component {
constructor(props) {
super(props)
this.inputRef = React.createRef();
}
componentDidMount() {
console.log(this.inputRef)
this.inputRef.current.focus();
}
render() {
return <input ref={this.inputRef} />
}
}
在上面的代码实例中,我们使用了 createRef
创建了一个 ref
,将其挂到了原生DOM元素 input 上面,这时候,我们就可以通过 ref.current
获取到这个DOM元素,并且可以调用上面的方法。
ref 如果挂载到了一个Class 组件上面,ref.current 获取到的就是这个 Class 组件的实例,也可以访问该 Class组件的方法。
就拿上面这个页面来说,下面的【图纸计划】是一个子组件,当我们将 ref
挂载到该子组件中时,对应的父组件就可以通过 this.ref.current
调用该子组件上面的方法,比如下拉框展开的回调、下拉框值变化的回调等等。
ref 回调函数
会在组件被挂载之后将组件实例传递给函数,函数组件不同于Class组件,函数组件没有实例。所以在正常情况下,ref 是不能挂载函数组件上的。
不过,函数组件是可以创建 ref 的,和类组件不同,函数组件使用 useRef
.
import React, { memo, useEffect, useRef, useState } from 'react';
const Parent = () => {
const inputRef = useRef(null)
input childRef = useRef(null)
return (
<>
<input ref={inputRef} onChange={onChange} />
<Child ref={childRef} handSave={handSave} />
</>
)
}
export default memo(Parent)
注意:这样的写法从语法上来说没问题,但是如果想在函数组件中通过 ref.current
获取 DOM元素或者子组件的实例,是拿不到的,需要其他的写法。
综上所述,可以总结以下几点:
那么对于函数式组件,我们真的没有办法获取并调用子组件上面的方法了吗?
答案:怎么可能,我们可以通过使用 forwardRef
来解决这个问题。
forwardRef (引用传递)是一种通过组件向子组件自动传递 引用 ref 的技术。
一句话概括:React 使用 forwardRef 完成 ref 的 透传,让函数式组件可以正常获取到子组件上面的方法。
import React, { memo, forwardRef, useRef, useImperativeHandle } from 'react';
const App = () => {
const childRef = useRef(null)
const getFocus = () => {
// 调用子组件的方法
childRef.current.inputFocus()
// 也可以调用暴露出来的其他值
childRef.current.setData(20)
}
return (
<>
<Child ref={childRef}/>
<button onClick ={getFocus}>点击获取焦点<button/>
</>
)
}
// 子组件
const Child = forwardRef((props, ref) => {
const inputRef = useRef(null)
const [data, setData] = useState('10')
// 使输入框获取焦点的方法
const inputFocus = () => {
inputRef.current.ocus()
}
// 输入框内容改变回调
const changeValue = () => {
console.log('哈哈哈')
}
// 将该方法暴露给父组件
useImperativeHandle(ref, () => ({
inputFocus,
changeValue,
data,
setData
}))
return <input ref={inputRef} onChange={changeValue}>
});
forwardRef
可以直接包裹一个函数式组件 ,被包裹的函数式组件会获得被分配给自己的ref(作为第二个参数)。如果直接将 ref 分配给没有被 forwardRef 包裹的函数式组件,React 会在控制台会报错。
forwardRef
会创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中,也即是透传。
注意:forWardRef 的参数必须是 function
直接使用 forwardRef
,无法控制要暴露给父组件的值,所以我们使用 useImperativeHandle
来控制要将哪些东西暴露给父组件。
注意: useImperativeHandle 要与 forwardRef 一起使用。
调用方式如上面的代码实例所示。
useImperativeHandle 为我们提供了一个类似实例的东西,帮助我们通过 useImperativeHandle 的 第二个参数,将所返回的对象的内容挂载到附件的 ref.current 上
父、子组件再使用该 hooks 时步骤如下:
useRef
创建一个 ref 对象,将这个 ref 对象赋给子组件的 ref 属性。forwardRef
包裹自己,允许作为函数组件的自己使用 ref。然后使用 useImperativeHandle
钩子函数,在该钩子函数的第二个函数参数中返回一些状态或方法,这个被返回的状态或方法就可以被父组件访问到。current
属性获取子组件暴露出的状态或方法。在 函数式组件中使用 forwardRef
与 useImperativeHandle
,有一个特殊的点需要注意:子组件的传参写法。如下所示:
// 可以使用这样的写法
<Child ref={childRef} list={drawingPlanList} editable={!isDisabled}/>
// 但不用采用这样的写法:这样的 写法有问题,具体原因待排查
const childProps = {
ref: {childRef},
list: {drawingPlanList},
editable: {!isDisabled}
}
< Child {...childProps}/>