React Refs

有些情況下,你會需要直接存取和操作 DOM 元素 (element),像是要 focus 表單欄位,React 提供了 ref (Reference) 屬性 (attribute) 來處理這個需求。

React 從 v16.3 開始改用不同的語法來建立 ref,以下會分別說明。

從 React v16.3 開始 - createRef()

我們可以用 React.createRef() 來建立 ref

在 React v16.3 之前的版本 - callback

ref 需要設定一個 callback function,這個 function 會在元件被掛載 (mount) 及卸載 (unmount) 時被執行,當這個 callback function 執行時會傳入 DOM 元素的參照 (reference),這時你可以將這個 reference 先存起來,稍後就可以使用它來存取 DOM 元素囉。

ref 可以設在 HTML element,也可以設在 Component 上:

  • DOM Element

    如果 ref 是設在 HTML 元素上面,ref function 被執行時,會傳入一個參數,表示實際的 DOM element,你可以將這個 reference 存下來供後續使用。

    例子:

    class CustomTextInput extends React.Component {
      constructor(props) {
        super(props);
        this.focusTextInput = this.focusTextInput.bind(this);
      }
      
      // 按鈕 onClick 事件處理函示
      focusTextInput() {
        // this.textInput 指向輸入框 DOM 元素
        // 執行 focus() DOM API 來聚焦游標
        this.textInput.focus();
      }
      
      render() {
        // 用 ref callback function 將輸入框的參照 (reference) 存到元件實例的 this.textInput 屬性上
        return (
          <div>
            <input
              type="text"
              ref={(input) => { this.textInput = input; }} />
            <br /><br />
            <input
              type="button"
              value="點我 focus 輸入欄位"
              onClick={this.focusTextInput}
            />
          </div>
        );
      }
    }
      
    ReactDOM.render(
      <CustomTextInput />,
      document.getElementById('root')
    );
    

    點我看這個例子的結果

    React 會在元件 mount 時,執行 ref callback 並傳進去 DOM element;而在 unmount 時,會再執行 ref callback 並傳進去 null。而 ref 的執行時間點,是在 componentDidMountcomponentDidUpdate lifecycle hooks 被觸發之前。

  • Class Component

    ref 是用在 component 上,callback function 被執行時,傳進去的參數是該元件的實例 (instance),通常是用來讓父元件可以直接存取子元件的屬性 (property) 或函式 (method)。

    例如延續上面的例子,我們想在元件被渲染到畫面時,自動 focus 在輸入欄位上:

    class AutoFocusTextInput extends React.Component {
      // 當元件掛載到 DOM 時
      // 執行 CustomTextInput 元件的 focusTextInput() 方法 focus 欄位
      componentDidMount() {
        this.textInput.focusTextInput();
      }
      
      render() {
        // 將 CustomTextInput 元件的 reference 存到 this.textInput
        return (
          <CustomTextInput
            ref={(input) => { this.textInput = input; }} />
        );
      }
    }
      
    ReactDOM.render(
      <AutoFocusTextInput />,
      document.getElementById('root')
    );
    

    點我看這個例子的結果

    注意只有 Class Component 能設定 ref 屬性,Functional Component 則不行,因為 Functional Component 沒有 instance。

如果你想將子元件中的某個 DOM reference 讓父元件能直接存取,你也可以用這種常用的寫法:

// 這種用法就可以用在 Functional Component
function CustomTextInput(props) {
  return (
    <div>
      // ref 設定父元件傳進來的 callback props
      // ref 執行時會自動將 DOM element reference 傳進去
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      // 從父元件傳進一個 callback props
      // 用來讓子元件的 ref 執行,並傳進 reference
      // 父元件在 callback 中,將 reference 存到自己的 this.inputElement 屬性上
      // P.S. inputRef 這名稱是自己可以隨便取的
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

最後提醒大原則上,能不用 ref 就儘量不用,因為容易破壞元件的獨立性和封裝性,在大多數的情況下,你可以透過傳遞 props提升共用狀態來解決你的問題。