React 之【父子组件传值 通讯】

在日常开发过程中,我们经常需要使用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子组件:

React 之【父子组件传值 通讯】

函数子组件:

React 之【父子组件传值 通讯】
如上可知,在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 之【父子组件传值 通讯】

可以看到可以获取到子组件的方法了.

但是最近在做一个需求时,我又发现上述方法不管用了,这是为什么呢?

在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组件,无法获取我们真实想要的子组件,更别说调用子组件的方法了。

React 之【父子组件传值 通讯】

怎么解决?

父组件不做修改,子组件改成下面这种,因为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} />);

React 之【父子组件传值 通讯】

完美解决

函数式父组件向函数式子组件利用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没多久,还需要沉淀。

上一篇:vue3的一些基本常识(slot,ref)


下一篇:性能分析