react中的Refs 轉(zhuǎn)發(fā)

Ref 轉(zhuǎn)發(fā)是一項(xiàng)將 ref 自動(dòng)地通過組件傳遞到其一子組件的技巧。對于大多數(shù)應(yīng)用中的組件來說,這通常不是必需的。但其對某些組件,尤其是可重用的組件庫是很有用的。最常見的案例如下所述。

轉(zhuǎn)發(fā) refs 到 DOM 組件

考慮這個(gè)渲染原生 DOM 元素 button 的 FancyButton 組件:

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

React 組件隱藏其實(shí)現(xiàn)細(xì)節(jié),包括其渲染結(jié)果。其他使用 FancyButton 的組件通常不需要獲取內(nèi)部的 DOM 元素 buttonref。這很好,因?yàn)檫@防止組件過度依賴其他組件的 DOM 結(jié)構(gòu)。

雖然這種封裝對類似 FeedStoryComment 這樣的應(yīng)用級組件是理想的,但其對 FancyButtonMyTextInput 這樣的高可復(fù)用“葉”組件來說可能是不方便的。這些組件傾向于在整個(gè)應(yīng)用中以一種類似常規(guī) DOM buttoninput 的方式被使用,并且訪問其 DOM 節(jié)點(diǎn)對管理焦點(diǎn),選中或動(dòng)畫來說是不可避免的。

Ref 轉(zhuǎn)發(fā)是一個(gè)可選特性,其允許某些組件接收 ref,并將其向下傳遞(換句話說,“轉(zhuǎn)發(fā)”它)給子組件。

在下面的示例中,F(xiàn)ancyButton 使用 React.forwardRef 來獲取傳遞給它的 ref,然后轉(zhuǎn)發(fā)到它渲染的 DOM button:

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

// 你可以直接獲取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

這樣,使用 FancyButton 的組件可以獲取底層 DOM 節(jié)點(diǎn) button 的 ref ,并在必要時(shí)訪問,就像其直接使用 DOM button 一樣。

以下是對上述示例發(fā)生情況的逐步解釋:

  1. 我們通過調(diào)用 React.createRef 創(chuàng)建了一個(gè) React ref 并將其賦值給 ref 變量。
  2. 我們通過指定 ref 為 JSX 屬性,將其向下傳遞給 <FancyButton ref={ref}>。
  3. React 傳遞 refforwardRef 內(nèi)函數(shù) (props, ref) => ...,作為其第二個(gè)參數(shù)。
  4. 我們向下轉(zhuǎn)發(fā)該 ref 參數(shù)到 <button ref={ref}>,將其指定為 JSX 屬性。
  5. 當(dāng) ref 掛載完成,ref.current 將指向 <button> DOM 節(jié)點(diǎn)。
注意

第二個(gè)參數(shù) ref 只在使用 React.forwardRef 定義組件時(shí)存在。常規(guī)函數(shù)和 class 組件不接收 ref 參數(shù),且 props 中也不存在 ref。

Ref 轉(zhuǎn)發(fā)不僅限于 DOM 組件,你也可以轉(zhuǎn)發(fā) refs 到 class 組件實(shí)例中。

組件庫維護(hù)者的注意事項(xiàng)

當(dāng)你開始在組件庫中使用 forwardRef 時(shí),你應(yīng)當(dāng)將其視為一個(gè)破壞性更改,并發(fā)布庫的一個(gè)新的主版本。 這是因?yàn)槟愕膸炜赡軙?huì)有明顯不同的行為(例如 refs 被分配給了誰,以及導(dǎo)出了什么類型),并且這樣可能會(huì)導(dǎo)致依賴舊行為的應(yīng)用和其他庫崩潰。

出于同樣的原因,當(dāng) React.forwardRef 存在時(shí)有條件地使用它也是不推薦的:它改變了你的庫的行為,并在升級 React 自身時(shí)破壞用戶的應(yīng)用。

在高階組件中轉(zhuǎn)發(fā) refs

這個(gè)技巧對高階組件(也被稱為 HOC)特別有用。讓我們從一個(gè)輸出組件 props 到控制臺的 HOC 示例開始:

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;
}

“l(fā)ogProps” HOC 透傳(pass through)所有 props 到其包裹的組件,所以渲染結(jié)果將是相同的。例如:我們可以使用該 HOC 記錄所有傳遞到 “fancy button” 組件的 props:

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

  // ...
}

// 我們導(dǎo)出 LogProps,而不是 FancyButton。
// 雖然它也會(huì)渲染一個(gè) FancyButton。
export default logProps(FancyButton);

上面的示例有一點(diǎn)需要注意:refs 將不會(huì)透傳下去。這是因?yàn)?ref 不是 prop 屬性。就像 key 一樣,其被 React 進(jìn)行了特殊處理。如果你對 HOC 添加 ref,該 ref 將引用最外層的容器組件,而不是被包裹的組件。

這意味著用于我們 FancyButton 組件的 refs 實(shí)際上將被掛載到 LogProps 組件:

import FancyButton from './FancyButton';

const ref = React.createRef();

// 我們導(dǎo)入的 FancyButton 組件是高階組件(HOC)LogProps。
// 盡管渲染結(jié)果將是一樣的,
// 但我們的 ref 將指向 LogProps 而不是內(nèi)部的 FancyButton 組件!
// 這意味著我們不能調(diào)用例如 ref.current.focus() 這樣的方法
<FancyButton
  label="Click Me"
  handleClick={handleClick}
  ref={ref}
/>;

幸運(yùn)的是,我們可以使用 React.forwardRef API 明確地將 refs 轉(zhuǎn)發(fā)到內(nèi)部的 FancyButton 組件。React.forwardRef 接受一個(gè)渲染函數(shù),其接收 props 和 ref 參數(shù)并返回一個(gè) React 節(jié)點(diǎn)。例如:

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;

      // 將自定義的 prop 屬性 “forwardedRef” 定義為 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回調(diào)的第二個(gè)參數(shù) “ref”。
  // 我們可以將其作為常規(guī) prop 屬性傳遞給 LogProps,例如 “forwardedRef”
  // 然后它就可以被掛載到被 LogProps 包裹的子組件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

在 DevTools 中顯示自定義名稱

React.forwardRef 接受一個(gè)渲染函數(shù)。React DevTools 使用該函數(shù)來決定為 ref 轉(zhuǎn)發(fā)組件顯示的內(nèi)容。

例如,以下組件將在 DevTools 中顯示為 “ForwardRef”:

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

如果你命名了渲染函數(shù),DevTools 也將包含其名稱(例如 “ForwardRef(myFunction)”):

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

你甚至可以設(shè)置函數(shù)的 displayName 屬性來包含被包裹組件的名稱:

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

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

  // 在 DevTools 中為該組件提供一個(gè)更有用的顯示名。
  // 例如 “ForwardRef(logProps(MyComponent))”
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容