react函数式组件之兄弟组件中的方法互相调用

最新更新时间:2022年01月15日14:40:31
《猛戳-查看我的博客地图-总有你意想不到的惊喜》

本文内容:一个父组件和两个子组件,三个组件都是函数式组件,子组件1需要调用子组件2的方法,同时子组件2需要调用子组件1的方法

技术方案分析

兄弟组件的方法互调
  • 子组件1 -> 父组件 -> 子组件2

子组件1通过props调用父组件的方法,父组件通过ref2调用子组件2的方法

  • 子组件2 -> 父组件 -> 子组件1

子组件2通过props调用父组件的方法,父组件通过ref1调用子组件1的方法

父组件调用子组件的方法
  • 父组件通过ref获取子组件实例,然后再调用方法
  • 子组件需要用到两个hooks,forwardRef和useImperativeHandle
  • forwardRef,用户 转发refs到DOM组件 和 在高阶组件中转发refs
  • useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。useImperativeHandle应当和forwardRef配合使用
获取通过Form.create()()创建的函数式组件的实例
  • 通过子组件的wrappedComponentRef属性传递ref
获取被connect包裹的函数式组件的实例
  • 通过forwardRef包裹connect

注意:被withRouter、connect、Form.create等方法包裹的组件并不能抛出ref,需要使用forwardRef抛出子组件的ref

示例代码

父元素
import React, {useRef} from 'react'

import Child1 form '../child1';
import Child2 form '../child2';

function Parent(props){
	const child1Ref = useRef(null);
	const child2Ref = useRef(null);
	
	const child1CallChild2 = ()=>{
		console.log('父元素调用子元素2的方法')
		child2Ref.current && child2Ref.parentCallChild2()
	}
	const child2CallChild1 = ()=>{
		console.log('父元素调用子元素1的方法')
		child1Ref.current && child1Ref.parentCallChild1()
	}
	
	return <div>
		<Child1 wrappedComponentRef={child1Ref} child1CallChild2={child1CallChild2}/>
		<Child2 ref={child2Ref} child2CallChild1={child2CallChild1} />
	</div>
}
export default Parent
子元素1
  • 通过Form.create()(Component)创建的函数式组件,对外暴露实例和方法
import React, {forwardRef, useImperativeHandle} from 'react'
import {Form} from 'antd'

function Child1(props, ref){
	function parentCallChild1(){
		console.log('父组件调用子组件1的方法')
	}
	function c1Callc2(){
		console.log('兄弟元素通信 子组件1调用子组件2的方法')
		props.child1CallChild2()
	}
	// 将parentCallChild1方法暴露给父组件获取的实例
	useImperativeHandle(ref,()=>({parentCallChild1}))
	
	return <div onClick={this.c1Callc2}>
		<Form></Form>
	</div>
}

const FormComponentWrap = Form.create()(forwardRef(Child1))
export default FormComponentWrap
子元素2
  • 通过connect()()创建的函数式组件,对外暴露实例和方法
import React, {forwardRef, useImperativeHandle} from 'react'
import { connect } from 'react-redux'

function Child2(props, ref){
	function parentCallChild2(){
		console.log('父组件调用子组件2的方法')
	}
	function c2Callc1(){
		console.log('兄弟元素通信 子组件2调用子组件1的方法')
		props.child2CallChild1()
	}
	// 将parentCallChild2方法暴露给父组件获取的实例
	useImperativeHandle(props.refInstance,()=>({parentCallChild2}))
	
	return <div onClick={this.c2Callc1}>Child2</div>
}

const Component = connect(matStateToProps,mapDispatchToProps)(Child2)
export default forwardRef((props, ref)=> <Component {...props} refInstance={ref} /> )

connect方法,连接react组件与redux中的store,将数据和UI连接起来,如果不写mapStateToProps参数,UI 组件就不会订阅Store, Store 的更新不会引起 UI 组件的更新;
matStateToProps是输入源,更新props;
mapDispatchToProps是输出源,更新action;

老版本框架获取实例

//父组件
this._childRef.current.getWrappedInstance()

//子组件
@connect(matStateToProps, mapDispatchToProps, mergeProps, { withRef: true })
export default connect(matStateToProps, mapDispatchToProps, mergeProps, { forwardRef: true })
export default connect(matStateToProps, mapDispatchToProps, mergeProps, { withRef: true })

获取类组件的实例

import React, {} from 'react'
import Child from '../child'
class Parent extends React.Component{
	constructor(){
		// this._childref = React.createRef();
	}
	callChildFunc(){
		//获取子组件实例 并调用方法
		this.refs._childref.getWrappedInstance().getName();
		this.refs._childref.wrappedInstance.getName();
		// this._childref.getName();
	}
	
	render(){
		return <Child ref={'_childref'} />
		// return 
		// return  this._childref = node } />
	}
}

class Child extends React.Component{

	getName(){
		console.log('wan')
	}
	render(){
		return <Child ref={'_childref'} />
	}
}

withRouter的使用

高阶组件withRouter,是将一个组件包裹进Route里面,这个组件的this.props可以获取到react-router的三个对象history, location, match

对于不是通过路由跳转过来的组件,比如首页,如果想通过props获取路由信息,就需要用withRouter包裹这个组件

或者,比如通过this.props.history.push(’/1’)跳转页面,报错history是undefined,需要用withRouter包裹组件

import React,{Component} from 'react'
import {withRouter} from 'react-router-dom'

class App extends Component{
    //被withRouter包裹后才能获取history, match, location等路由信息对象
    console.log(this.props);
    
    constructor(props){
	  super(props);
	  // 通过history监听location变化 动态修改title
	  props.history.listen((location) => {
	    switch(location.pathname){
	      case '/1/1' :
	        document.title = '第一页';
	        break;
	      case '/1/2' :
	        document.title = '第二页';
	        break;
	      default : break;
	    }
	  })
	}
	
    render(){}
}
export default withRouter(App);

扩展-父元素调用子元素的方法

父-类组件 子-类组件

父-类组件 子-函数组件

父-函数组件 子-类组件

import Child from './Child'
const childRef = React.createRef()
function Parent(){
	//获取子元素实例的方法或变量
	//childRef.current.name
	//childRef.current.eat()
	return <Child ref={childRef} />
}

class Child extends React.PureComponent{
	constructor(props){
		super(props);
		this.eat = this.eat.bind(this);
		this.name = 'wanshaobo';
	}

	eat(){
		console.log('eat');
	}

	render(){
		return <div>1</div>
	}
}

父-函数组件 子-函数组件

import Child from './Child'
const childRef = React.createRef()
function Parent(){
	//获取子元素实例的方法或变量
	//childRef.current.name
	//childRef.current.eat()
	return <Child ref={childRef} />
}

import React, {forwardRef, useImperativeHandle} from 'react'
function Child(props, ref){
	const name = 'wanshaobo';
	const eat = ()=>{
		console.log('eat');
	}
	useImperativeHandle(ref, ()=>({name,eat}));
	return <div>1</div>
}
export default forwardRef(Child);
参考资料
  • react中父组件调用子组件的方法
  • react-redux的connect用法详解
  • React 中connect和forwardRef连用会导致传入子组件的ref失效

感谢阅读,欢迎评论^-^

打赏我吧^-^

你可能感兴趣的:(react,react.js,javascript,前端)