在日常开发过程中,我们经常需要使用ref,来做一些逻辑处理。对于当前组件来说,ref的使用比较方便,而对于跨组件使用,尤其是现在hooks的广泛运用和以前的class的交叉使用,有时候会让我们使用上出现一些问题。比如在父组件中引入子组件,把ref传入到函数子组件中,会一直为空的问题。
class父组件与class子组件及函数式子组件利用ref进行通讯
父组件-class类组件
//测试调用子组件方法
class Home extends React.Component {
childRef = null;
render() {
return (
<div>
<Test ref={ref => this.childRef = ref} />
<Button onClick={() => {
console.log(this.childRef)
}}>
</Button>
</div>
)
}
}
子组件-class组件
class ForwardRef extends React.Component {
test = () => {
console.log('我是ForwardRef组件的test方法')
}
render(){
return (
<div>
ForwardRef组件
</div>
)
}
}
export default ForwardRef;
子组件-函数组件
const ForwardRef = (props) => {
console.log(props)
return (
<div>
ForwardRef组件
</div>
)
}
export default ForwardRef;
我们分别在控制台输出一下props
class子组件:
函数子组件:
如上可知,在class组件中传递ref给函数组件,接收到的都是undefined,而class组件是能接收父组件传过来的ref的。那怎么能把ref从class父组件传递给函数子组件呢?
React官网中提到 useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
在本例中,渲染 <FancyInput ref={inputRef} />
的父组件可以调用 inputRef.current.focus()
。
回到我们的例子:
class Home extends React.Component {
childRef = null;
render() {
return (
<div>
<Test ref={ref => this.childRef = ref} />
<Button onClick={() => {
if(this.childRef){
console.log(this.childRef.test())
}
}}>
</Button>
</div>
)
}
}
export default connect()(Home);
import React, {
useImperativeHandle,
forwardRef
} from 'react';
const ForwardRef = forwardRef((props, ref) => {
console.log(props)
useImperativeHandle(ref, () => ({
test,
}));
const test = () => {
console.log('我是ForwardRef组件的test方法')
}
return (
<div>
ForwardRef组件
</div>
)
})
export default ForwardRef;
可以看到可以获取到子组件的方法了.
但是最近在做一个需求时,我又发现上述方法不管用了,这是为什么呢?
在react项目中,我们经常会用到HOC组件,比如为了页面出错而不至于全局崩溃的error包装组件,每个组件出错只把当前组件出错显示,而整体页面不至于崩溃。比如侧边栏、弹窗、tab切换等打开的时候,如果当前组件崩溃,只显示打印当前出错的组件显示部位,用户体验不会太差。还有我们经常使用的全局数据管理,比如dva的connect包装组件,使得组件可以使用dva的全局状态。
当前父组件代码如下:
import React from 'react';
import { connect } from 'dva';
import { Button } from 'antd';
import Test from './component/forwardRef';
class Home extends React.Component {
childRef = null;
render() {
return (
<div>
<Test ref={ref => this.childRef = ref} />
<Button onClick={() => {
if(this.childRef){
console.log(this.childRef)
}
}}>
</Button>
</div>
)
}
}
export default connect()(Home);
子组件为函数组件,被connect包装。
import React, {
} from 'react';
import { connect } from 'dva';
const ForwardRef = (props) => {
console.log(props)
const test = () => {
console.log('我是ForwardRef组件的test方法')
}
return (
<div>
ForwardRef组件
</div>
)
}
const ForwardRefWrap = connect()(ForwardRef)
export default ForwardRefWrap;
此时我们在父组件通过ref调用子组件的时候,会发现打印的其实是子组件被connect包装的HOC组件,无法获取我们真实想要的子组件,更别说调用子组件的方法了。
怎么解决?
父组件不做修改,子组件改成下面这种,因为forwardRef函数是作为把父组件的ref传递给子组件中,并且进行包装,props和ref进行分开传递,所以应该是connect进行包装,forwardRef在最外层进行包装,才能把父组件的ref传入到子组件中。
import React, {
useImperativeHandle,
forwardRef
} from 'react';
import { connect } from 'dva';
const ForwardRef = (props) => {
console.log(props)
const {refInstance} = props;
useImperativeHandle(refInstance, () => ({
test,
}));
const test = () => {
console.log('我是ForwardRef组件的test方法')
}
return (
<div>
ForwardRef组件
</div>
)
}
const ForwardRefWrap = connect()(ForwardRef)
export default forwardRef((props, ref) => <ForwardRefWrap {...props} refInstance={ref} />);
完美解决
函数式父组件向函数式子组件利用ref传值
父组件
import React, {
useRef
} from 'react';
const ForwardRef = (props) => {
const dataInfo = useRef()
const handleSubmit = () => {
const dataInfoSource = dataInfo?.current?.getVal()
//测试拿到子组件状态及方法
console.log(dataInfoSource)
};
return (
<div>
<Child cRef={dataInfo} />
<Button onClick={handleSubmit}>
</div>
)
}
export default ForwardRef;
子组件
import React, {
useImperativeHandle,
forwardRef
} from 'react';
import { connect } from 'dva';
const indexInfoCard = (props) => {
const {cRef} = props;
useImperativeHandle(cRef, () => ({
test,
}));
const test = () => {
console.log('我是ForwardRef组件的test方法')
}
return (
<div>
ForwardRef组件
</div>
)
}
const ForwardRefWrap = connect()(ForwardRef)
export default forwardRef((props, ref) => <ForwardRefWrap {...props} refInstance={ref} />);
完美解决,总结也是为了加深记忆,因为刚接触React没多久,还需要沉淀。