React Refs

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

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提升共用狀態來解決你的問題。