在 React 組件中使用 Refs 指南


原文:Fullstack React's Guide to using Refs in React Components
作者:Yomi Eluwande
譯者:博軒

譯文:https://segmentfault.com/a/1190000019277029

使用 React 時,我們的默認思維方式應該是 不會強制修改 DOM ,而是通過傳入 props 重新渲染組件。但是,有些情況卻無法避免修改 DOM 。

React 中的 Refs 提供了一種訪問 render() 方法中創建的 React 元素(或 DOM 節點)的方法。

當父組件需要與子組件交互時,我們通常使用 props 來傳遞相關信息。 但是,在某些情況下,我們可能需要修改子項,而不用新的props 重新呈現 (re-rendering) 它。 這時候就需要 refs 出場了。

我什么時候應該使用 Refs ?

我們建議在以下情況下使用 refs

  • 與第三方 DOM 庫集成
  • 觸發命令式動畫
  • 管理焦點,文本選擇或媒體播放
譯注:第三點是否也可以理解為使用  event 對象呢?在 React 中就是合成事件(SyntheticEvent)。
官方文檔中提到:避免使用  refs 來做任何可以通過聲明式實現來完成的事情。

所以一旦我們確定我們真的應該使用 refs,我們需要如何使用它們呢?

在 React 中使用 Refs

您可以通過多種方式使用 refs :

  • React.createRef()
  • 回調引用 (Callback refs)
  • String refs(已過時)
  • 轉發 refs (Forwarding refs)

接下來,讓我們看看每一種實現方式:

React.createRef()

可以使用該 React.createRef() 函數創建 Refs ,並通過該 ref 屬性附加到 React 組件中的 HTML 元素。

通常在組件的構造函數內創建 ref ,使其在整個組件中可用。例如:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.firstRef = React.createRef();
  }
  render() {
    return <div ref={this.firstRef} />;
  }}

如上所示:

  • 一個 ref 實例在構造函數中創建,並賦值給 this.firstRef

  • 在 render() 方法內部,將構造函數中創建的 ref 傳遞給 div

接下來,讓我們看一個在 React 組件中使用 refs 的示例。

使用 Refs 聚焦輸入

這是另一個例子:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Explicitly focus the text input using the raw DOM API
    // Note: we're accessing "current" to get the DOM node
    this.textInput.current.focus();
  }

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <input type="text" ref={this.textInput}/>

        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

在上面的代碼塊中,我們構建了一個按鈕,當單擊它時,該頁面會自動聚焦在輸入框上。

首先,我們在構造方法中創建一個 ref 實例,並將其賦值給 this.textInput,然后通過 ref 屬性將其分配給 input 元素。

<input type="text" ref={this.textInput} />

注意,當 ref 屬性被一個 HTML 元素使用時(比如當前示例中的 input 元素),在 constructor 中使用 React.createRef() 創建的 ref 會接收來自底層 DOM 元素的 current 值。

譯注:這里的 current 應該是 合成事件(SyntheticEvent)

這意味着訪問 DOM 值,我們需要寫這樣的東西:

this.textInput.current;

第二個元素是一個按鈕,點擊它之后會自動聚焦到第一個輸入框上面。我們為 onClick 屬性設置了 this.focusTextInput 函數。

<input
  type="button"
  value="Focus the text input"
  onClick={this.focusTextInput}/>;

函數 focusTextInput() 使用了 JavaScript 構建 DOM 的標准函數。 .focus() 方法會將光標聚焦於文本輸入框上。

focusTextInput() {
  this.textInput.current.focus();}

最后,focusTextInput 函數綁定在這樣的 constructor 方法中的:

this.focusTextInput = this.focusTextInput.bind(this);

從 ref 中獲取值

在這個例子中,我們將看到如何為 input 輸入框設置 ref 屬性,並通過 ref 來獲取值。示例如下:

在這個例子中,我們創建了一個 input 輸入框來輸入值。然后,當單擊提交按鈕時,我們將讀取此值,並在控制台打印。

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // create a ref to store the textInput DOM element
    this.textInput = React.createRef();
  }

  handleSubmit = e => {
    e.preventDefault();

    console.log(this.textInput.current.value);
  };

  render() {
    // tell React that we want to associate the <input> ref
    // with the `textInput` that we created in the constructor
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.textInput}/>
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

同樣,我們使用該 React.createRef() 函數創建一個 ref 實例,然后將它分配給實例變量 this.textInput。

在 render 函數中,我們希望讀取 form 下輸入框的值。我們如何讀取這個值? 通過為 input 指定一個 ref ,然后讀取 ref 的值。

<input type="text" ref={this.textInput} />

點擊提交按鈕,上面示例中 form 元素會通過 onSubmit 方法,調用 this.handleSubmit 函數 ,並在控制台打印輸入框中的信息。

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput);};

上面,參數 e 包含事件對象。我們使用e.preventDefault() 來告訴瀏覽器我們正在處理被點擊的提交按鈕,我們不希望這個事件“冒泡”(意思就是說,阻止瀏覽器的默認行為)。
譯注:這里可以看一下 React 對於事件的處理:在 React 中另一個不同點是你不能通過返回 false 的方式阻止默認行為。你必須顯式的使用 preventDefault

在上面示例中,我們打印了 this.textInput ,在控制台可以看到一個 ref 對象。

> Object {current: HTMLInputElement}

請注意,它有一個 current屬性,即 HTMLInputElement 。這是 input DOM 元素本身,而不是實際值。 我們必須使用 this.textInput.current.value 來獲取 input 標簽的實際值:

handleSubmit = e => {
  e.preventDefault();
  console.log(this.textInput.current.value);};

使用 refs 是一種從表單中直接提取值的方式:只需要給 input 標簽設置 ref ,並在你需要的時候將值提取出來。

Refs 回調

Refs 回調 是在 React 中使用 ref 的另一種方式。要以這種方式使用 ref,我們需要為 ref 屬性設置回調函數。當我們設置 ref 時,React 會調用這個函數,並將 element 作為第一個參數傳遞給它。

這是另一個例子的代碼。像上面的示例一樣,此代碼獲取 input 標簽的文本值,但在這里我們使用回調引用:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };
  }

  handleSubmit = e => {
    e.preventDefault();
    console.log(this.textInput.value);
  };

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" ref={this.setTextInputRef}/>
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

上面的示例中,我們將 input 標簽的 ref 設置為 this.setTextInputRef。

當組件安裝時,React 會將 DOM 元素傳遞給 ref 的回調;當組件卸載時,則會傳遞 null。(ref 回調會在 componentDidMount 和 componentDidUpdate 生命周期之前調用。)

String Ref(已過時)

還有另一種設置 refs 的方法,但它被認為是過時的,可能很快就會被棄用。但是你可能會在其他人的代碼中看到它,所以這里說一下。

使用 string refs,你將會看到這樣的 input 標簽:

<input type="text" ref="textInput" />

然后,我們可以在組建上得到這樣的值:this.refs.textInput.value - 但是,再次聲明,這不應該在新代碼中使用,因為這個 API 將被棄用。

轉發 Refs (Forwarding Refs)

Ref forwarding 是一種將 ref 通過組件傳遞給其子節點的技術。它對於可復用組件庫和高階組件(HOC)等情況非常有用。

您可以使用 React.forwardRef 函數將 ref 轉發到組件。我們來看下面的例子:

const TextInput = React.forwardRef((props, ref) => (
  <input type="text" placeholder="Hello World" ref={ref}/>));
const inputRef = React.createRef();

class CustomTextInput extends React.Component {
  handleSubmit = e => {
    e.preventDefault();
    console.log(inputRef.current.value);
  };

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <TextInput ref={inputRef}/>
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

Ref forwarding 允許組件接收一個 ref ,並將它向下傳遞(換句話說,“轉發”它)給子組件。

在上面的示例中,我們使用 input 標簽創建了一個名為 TextInput 的組件。那么,我們如何將 ref 傳遞或轉發到 input 標簽呢?

首先,我們使用下面的代碼創建一個 ref :

const inputRef = React.createRef();

然后,我們將 ref 通過為組件 <TextInput ref={inputRef}> 指定一個同名的 JSX 的屬性,將 ref 向下傳遞。然后 React 將會把 ref 作為第二個參數轉發給 forwardRef 函數。

接下來,我們將此 ref 參數轉發給 <input ref={ref}>。現在可以在外層組件通過 inputRef.current 訪問DOM節點的值了。

轉發 refs 和高階組件

最后,讓我們看一下使用 refs 的另一個例子,但這次是使用高階組件(HOC)。

在上面的示例應用程序中,會將所有 input 標簽中輸入的值在控制台打印。這里已經為 input 標簽設置了 ref 屬性,接下來,讓我們看一下需要如何在高階組件中傳遞 / 轉發 ref 。

const Input = InputComponent => {
  const forwardRef = (props, ref) => {
    const onType = () => console.log(ref.current.value);
    return <InputComponent forwardedRef={ref} onChange={onType} {...props} />;
  };
  return React.forwardRef(forwardRef);
};

這里有一個名為 Input 的高階組件 ,它接受 InputComponent 作為參數。當用戶輸入的時候,他還會將 ref 的值在控制台打印。

在 Input 高階組件內,forwardRef 函數會返回 InputComponent。forwardRef 函數中所包含的 ref 參數,是由 React.forwardRef 函數創建的。 高階組件最終會將包裝好的組件作為值返回。

接下來,我們創建一個組件,將 input 作為子組件包含進來。

const TextInput = ({ forwardedRef, children, ...rest }) => (
  <div>
    <input ref={forwardedRef} {...rest} />
    {children}
  </div>);

上面的組件會將 forwardedRef 分配給 ref 屬性, 當渲染子組件的時候,input 輸入框就會接收到這個 ref 。…rest 是 props 的解構(也就是說,我們會將 rest 數組中的所有參數作為 props 傳遞給 input 組件)。那么我們該如何使用 TextInput 組件呢?像這樣:

const InputField = Input(TextInput);

class CustomTextInput extends Component {
  render() {
    const inputRef = React.createRef();

    return <InputField ref={inputRef}/>;
  }
}

最后,將 TextInput 傳入 Input 高階組件,會返回一個 InputField component。

創建一個 ref ,並作為參數傳遞給 InputField 組件。

結論

與通過 props 和 state 不同,Refs 是一種將數據傳遞給特定子實例的好方法。

你必須要小心,因為 refs 操縱實際的 DOM,而不是虛擬的 DOM,這與 React 思維方式相矛盾。因此,雖然 refs 不應該是通過應用程序流動數據的默認方法,但是當您需要時,它們是可以從 DOM 元素讀取數據的好方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM