[譯]React高級指引5:Refs轉發

原文連接:reactjs.org/docs/forwar…html

引言

Ref轉發是一項自動從組件中將ref傳遞給其子組件的技術。但這在絕大部分組件中不是必需的。可是這對某些組件來講會很是有用,尤爲是在某些可複用的第三方組件庫中。常見的場景咱們將在下面的內容描述。react

將refs轉發給DOM組件

咱們來考慮下面的例子,FancyButton組件渲染了一個原生button DOM元素:數組

function FancyButton(props) {
  return (
    <button className="FancyButton">
      {props.children}
    </button>
  );
}
複製代碼

React組件隱藏了它們的實現細節,包括它們的渲染輸出。使用FancyButton的其餘組件一般不須要獲取它內部的button DOM元素的ref。這是很是好的,由於它避免了組件過於依賴其餘組件的DOM結構。bash

儘管這樣封裝代碼對於應用層級的組件(好比FeedStoryComment)來講是理想的,但這對相似FancyButtonMyTextInput這類的高複用性的「葉」組件來講會很是不方便。這些組件會在應用中充當相似原生DOM buttoninput來使用,所以對於管理焦點,選擇或動畫效果來講獲取它們的DOM節點是不可避免的操做。app

Refs轉發是一個選擇性加入的特性,它讓組件接收它們收到的ref並將它傳遞(或稱爲轉發)到更深層級的子元素中。dom

在下面的例子中,FancyButton使用React.forwardRef獲取傳遞給它的ref,並轉發給它渲染的button元素。函數

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你如今能夠直接獲取DOM元素的引用了:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
複製代碼

用這種方法,使用了FancyButton組件的組件就獲得了FancyButton組件中的button元素的引用而且在必要時可使用它——就像直接操縱DOM元素同樣。動畫

下面是對上面的例子代碼運行的詳細解釋:ui

  1. 咱們通多調用React.createRef來建立一個React ref並將它賦值給ref變量。
  2. 經過聲明JSX屬性<FancyButton ref={ref}>ref傳遞給FancyButton
  3. React將ref做爲第二個參數傳遞進forwardRef內的(props, ref) => ...函數。
  4. 咱們經過聲明JSX屬性<button ref={ref}>ref轉發給button元素。
  5. 當ref被綁定時,咱們就能夠經過ref.current來獲取<button>DOM節點。

注意: 第二個參數ref只存在於當你調用React.forwardRef來定義組件時。正常的函數組件和class組件不會接收ref做爲參數,也沒法在props中獲取到ref。 . ref轉發並不侷限於在DOM元素上使用,你也能夠將ref轉發給一個class組件實例。this

組件庫維護者的注意事項

當你在組件庫中使用forwardRef時,你應該把它看成一個破環性的更改而且發佈一個新版本。由於你的組件庫會有和之前的版本顯著的不一樣(好比refs被分配給了誰,導出了聲明類型),這可能會破壞使用者的應用和那些依賴老版本的組件。

處於一樣的緣由,當React,forwardRef存在時有條件地調用它是咱們不推薦的:它改變了你的庫的行爲,並在用戶更新React時會破壞用戶的應用。

在高階組件中轉發refs

轉發refs對高階組件(也被稱爲HOC)十分有用。在下面的例子中咱們使用高階組件在控制檯打印它接收的props:

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}
複製代碼

高階組件logProps將全部的props傳遞給它包裹的組件,因此渲染的結果將會是相同的。在下面的例子中,咱們經過這個HOC將全部傳遞給「fancy button」組件的的props都打印出來:

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}
//咱們經過export LogProps來代替export FancyButton
//這依然會渲染FancyButton
export default logProps(FancyButton);
複製代碼

但在上面的例子中有一點須要注意:refs不會透傳下去,由於ref不是一個porp。就像key同樣,React處理它的方式是不一樣的。若是你爲HOC添加了一個ref,那麼這個ref將會成爲最外面的容器組件的引用,而不是被包裹組件的。

這意味着本來打算給FancyButton使用的ref實際上被綁定在了LogProps組件上:

import FancyButton from './FancyButton';

const ref = React.createRef();

//咱們引入的FancyButton組件其實是LogProps高階組件
//即便最終的渲染結果是相同的,
//咱們的ref指向的是LogProps而不是內部的FancyButton組件!
//這意味着咱們不能調用ref.current.focus()
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;
複製代碼

但幸運的是,咱們能夠經過顯式地調用React.forwardRef API將refs轉發給內部的FancyButton組件。React.forwardRef接收一個render函數(接收了propsref做爲參數)並返回一個React節點:

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;
      
      //將自定義的「forwardRef」最爲ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  //注意React.forwardRef提供的第二個參數「ref」
  //咱們能夠把它做爲一個常規prop傳遞給LogProps,
  //好比forwardRef。
  //這個forwardRef prop以後將會被Component綁定
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
複製代碼

在DevTools中顯式自定義名稱

React.forwardRef接收一個render函數。React DevTools使用這個函數來決定爲ref轉發組件顯示的內容。

好比下面的例子,組件將會在DevTools中顯示「ForwardRef「:

const WrappedComponent = React.forwardRef((props, ref) => {
  return <LogProps {...props} forwardedRef={ref} />;
});
複製代碼

若是你爲render函數起了名字,那麼DevTools中顯示的內容將會包含這個名字(好比:」ForwardRef(myFunction)」):

const WrappedComponent = React.forwardRef(
  function myFunction(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }
);
複製代碼

你甚至能夠經過設置函數的displayName屬性來使展現內容包括你包裹的組件:

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

  function forwardRef(props, ref) {
    return <LogProps {...props} forwardedRef={ref} />;
  }

  //給這個組件一個展現名稱
  //使其在DevTools中對用戶更有幫助
  //好比 "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}
複製代碼
相關文章
相關標籤/搜索