1. 程式人生 > 其它 >React 之【父子元件傳值 通訊】

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子元件:

函式子元件:


如上可知,在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沒多久,還需要沉澱。