react中父组件调用子组件的方法

前言

react中,我们经常会想到,通过父元素直接调用子元素的方法,实现高内聚的效果。而这个用法的写法却是,五花八门。如下所示:

1、类组件中的使用React.createRef()

  • 优点:
    1、写法简单易懂
  • 缺点:
    1、假如子组件是嵌套了HOC,就无法指向真实子组件
import React , { Component } from "react"
class Child extends Component {
	func(){
		console.log("执行我")
	}
	render(){
		return (<div>子组件</div>);
	}
}
class Parent extends Component {
	constructor(props) {
	    super(props);
	 	this.Child = React.createRef();
	}
	handleOnClick = ()=>{
		this.Child.current.func();
	}
	render(){
		return (<div>
			<button onClick={this.handleOnClick}>click</button>
			<Child ref={this.Child}></Child>	
		</div>);
	}
}

2、使用ref的函数式声明

  • 优点:
    1、写法更简单易懂
    2、没有多余的代码
  • 缺点:
    1、假如子组件是嵌套了HOC,就无法指向真实子组件
import React , { Component } from "react"
class Child extends Component {
	func(){
		console.log("执行我")
	}
	render(){
		return (<div>子组件</div>);
	}
}
class Parent extends Component {
	handleOnClick = ()=>{
		this.Child.func();
	}
	render(){
		return (<div>
			<button onClick={this.handleOnClick}>click</button>
			<Child ref={ node => this.Child = node }></Child>	
		</div>);
	}
}

3、使用props自定义onRef属性

  • 优点:
    1、写法简单易懂
    2、假如子组件是嵌套了HOC,也可以指向真实子组件
  • 缺点:
    1、需要自定义props属性
import React , { Component } from "react"
import { withRouter } from "react-router-dom"
// 使用装饰器给裹上一层高阶函数(装饰器需要安装对应的babel包,此处作为演示使用)
@withRouter 
class Child extends Component {
	componentDidMount(){
		this.props.onRef && this.props.onRef(this);
	}
	func(){
		console.log("执行我")
	}
	render(){
		return (<div>子组件</div>);
	}
}
class Parent extends Component {
	handleOnClick(){
		this.Child.func();
	}
	render(){
		return (<div>
			<button onClick={this.handleOnClick}>click</button>
			<Child onRef={ node => this.Child = node }></Child>	
		</div>);
	}
}

4、函数式和hooks写法

其实下面的缺点基本不算缺点了,因为函数式写法,下面算是简单的了。使用forwardRef只会让你的组件定义的更复杂

  • 优点:
    1、写法简单易懂
    2、假如子组件嵌套了HOC,也可以指向真实子组件
  • 缺点:
    1、需要自定义props属性
    2、需要自定义暴露的方法

父组件

import React from 'react';
import Child from './Child';
const Parent = () => {
  let ChildRef = React.createRef();
  function handleOnClick() {
    ChildRef.current.func();
  }
  return (
    <div>
      <button onClick={handleOnClick}>click</button>
      <Child onRef={ChildRef} />
    </div>
  );
};
export default Parent;

子组件

import React, { useImperativeHandle } from 'react';
import { withRouter } from 'react-router-dom';
const Child = props => {
  //用useImperativeHandle暴露一些外部ref能访问的属性
  useImperativeHandle(props.onRef, () => {
    return {
      func: func,
    };
  });
  function func() {
    console.log('执行我');
  }
  return <div>子组件</div>;
};
export default withRouter(Child);

5、使用forwardRef抛出子组件的ref

这个方法其实更适合自定义HOC。但问题是,withRouter、connect、Form.create等方法并不能抛出ref,假如Child本身就需要嵌套这些方法,那基本就不能混着用了。forwardRef本身也是用来抛出子元素,如input等原生元素的ref的,并不适合做组件ref抛出,因为组件的使用场景太复杂了。下面看代码实现:

父组件

import React from 'react';
import Child from './Child';
const Parent = () => {
  let ChildRef = React.createRef();
  function handleOnClick() {
    ChildRef.current.func();
  }
  return (
    <div>
      <button onClick={handleOnClick}>click</button>
      <Child ref={ChildRef} />
    </div>
  );
};
export default Parent;

子组件

import React, { Component } from 'react';
@Log
class Child extends Component {
  func = () => {
    console.log('打印了我');
  };
  render() {
    return <div>我是个测试的子组件</div>;
  }
}
// 自定义可以抛出子组件ref的HOC
function Log(Comp) {
  const Log = props => {
    const { forwardRef, ...rest } = props;
    return <Comp ref={forwardRef} {...rest} />;
  };
  return React.forwardRef((props, ref) => {
    return <Log {...props} forwardRef={ref} />;
  });
}
export default Child;

总结

父组件调子组件函数有两种情况

  • 子组件无HOC嵌套
  • 有HOC嵌套

选择什么方法,具体情况具体分析。 使用onRef自定义props的方式更实用,无论组件是否需要嵌套HOC,嵌套多少层,一把梭就行了,不用改组件里边的代码。一个写好的组件,只需要做加法就好了。