React中Refs的使用方法


轉載請注明出處原文鏈接地址 Vincent'Blog-React中Refs的使用方法

什么是Refs

Refs 提供了一種方式,允許我們訪問 DOM 節點或在 render 方法中創建的 React 元素。
Ref轉發是一項將ref自動通過組件傳遞到子組件的技巧。 通常用來獲取DOM節點或者React元素實例的工具。在React中Refs提供了一種方式,允許用戶訪問dom節點或者在render方法中創建的React元素。

Refs轉發

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

背景

在React單向數據流中,props是父子組件交互的唯一方式。要修改一個子組件,需要通過新的props來重新渲染。在有些情況下,需要在數據流之外強行修改子組件(組件或者Dom元素),那么可以通過Refs來進行修改子組件。

組件類型

受控組件

在 HTML 中,表單元素(如<input><textarea><select>)之類的表單元素通常自己維護 state,並根據用戶輸入進行更新。而在 React 中,可變狀態(mutable state)通常保存在組件的 state 屬性中,並且只能通過使用 setState()來更新。我們可以把兩者結合起來,使 React 的 state 成為“唯一數據源”。渲染表單的 React 組件還控制着用戶輸入過程中表單發生的操作。被 React 以這種方式控制取值的表單輸入元素就叫做“受控組件”。

非受控組件

不被React組件控制的組件。在受控制組件中,表單數據由 React組件自行處理。其中表單數據由DOM本身處理。文件輸入標簽就是一個典型的不受控制組件,它的值只能由用戶設置,通過DOM自身提供的一些特性來獲取。

受控組件與非受控組件的區別

受控組件和不受控組件最大的區別就是前者自身維護的狀態值變化,可以配合自身的change事件,很容易進行修改或者校驗用戶的輸入。在React中 因為 Refs的出現使得 不受控制組件自身狀態值的維護變得容易了許多。

使用場景

  • 對Dom元素的焦點控制、內容選擇、控制
  • 對Dom元素的內容設置及媒體播放
  • 對Dom元素的操作和對組件實例的操作
  • 集成第三方 DOM 庫

避免使用 refs 來做任何可以通過聲明式實現來完成的事情。

舉個例子,避免在 Dialog 組件里暴露 open() 和 close() 方法,最好傳遞 isOpen 屬性。

訪問 Refs

當 ref 被傳遞給 render 中的元素時,對該節點的引用可以在 ref 的 current 屬性中被訪問。

const node = this.myRef.current;

ref 的值根據節點的類型而有所不同:

  • 當 ref 屬性用於 HTML 元素時,構造函數中使用 React.createRef() 創建的 ref 接收底層 DOM 元素作為其 current 屬性。
  • 當 ref 屬性用於自定義 class 組件時,ref 對象接收組件的掛載實例作為其 current 屬性。
  • 不能再函數組件上使用Ref屬性,因為函數組件沒有實例。

例子

創建 Refs

Refs 是使用 React.createRef() 創建的,並通過 ref 屬性附加到 React 元素。在構造組件時,通常將 Refs 分配給實例屬性,以便可以在整個組件中引用它們。

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

DOM為元素添加ref

用 ref 去存儲 DOM 節點的引用:

class CustomTextInput extends React.Component {
 constructor(props) {
   super(props);
   // 創建一個 ref 來存儲 textInput 的 DOM 元素
   this.textInput = React.createRef();
   this.focusTextInput = this.focusTextInput.bind(this);
 }

 focusTextInput() {
   // 直接使用原生 API 使 text 輸入框獲得焦點
   // 注意:我們通過 "current" 來訪問 DOM 節點
   this.textInput.current.focus();
 }

 render() {
   // 告訴 React 我們想把 <input> ref 關聯到
   // 構造器里創建的 `textInput` 上
   return (
     <div>
       <input
         type="text"
         ref={this.textInput} />
       <input
         type="button"
         value="Focus the text input"
         onClick={this.focusTextInput}
       />
     </div>
   );
 }
}

組件會在掛在是給current屬性傳入DOM元素,並在組件卸載時傳入null。ref會在componentDidMount或者componentDidUpdate生命周期觸發前更新。

class組件添加ref

包裝上面的 CustomTextInput,來模擬它掛載之后立即被點擊的操作,我們可以使用 ref 來獲取這個自定義的 input 組件並手動調用它的 focusTextInput 方法:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

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

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

函數組件添加refs

默認情況下,不能再函數組件上使用ref屬性,因為函數組件沒有實例。如果要在函數組件上使用ref,需要使用useRefuseImperativeHandle結合使用。

useRef 返回一個可變的 ref 對象,其 current 屬性被初始化為傳入的參數(initialValue)

useRef 返回的 ref 對象在組件的整個生命周期內保持不變,也就是說每次重新渲染函數組件時,返回的ref 對象都是同一個(使用 React.createRef ,每次重新渲染組件都會重新創建 ref)

import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
    let [number, setNumber] = useState(0);
    return (
        <>
            <Child />
            <button onClick={() => setNumber({ number: number + 1 })}>+</button>
        </>
    )
}
let input;
function Child() {
    const inputRef = useRef();
    console.log('input===inputRef', input === inputRef);
    input = inputRef;
    function getFocus() {
        inputRef.current.focus();
    }
    return (
        <>
            <input type="text" ref={inputRef} />
            <button onClick={getFocus}>獲得焦點</button>
        </>
    )
}

forwardRef.

  • 因為函數組件沒有實例,所以函數組件無法像類組件一樣可以接收 ref 屬性
  • forwardRef 可以在父組件中操作子組件的 ref 對象
  • forwardRef 可以將父組件中的 ref 對象轉發到子組件中的 dom 元素上
  • 子組件接受 props 和 ref 作為參數
function Child(props,ref){
  return (
    <input type="text" ref={ref}/>
  )
}
Child = React.forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  // 在使用類組件的時候,創建 ref 返回一個對象,該對象的 current 屬性值為空
  // 只有當它被賦給某個元素的 ref 屬性時,才會有值
  // 所以父組件(類組件)創建一個 ref 對象,然后傳遞給子組件(類組件),子組件內部有元素使用了
  // 那么父組件就可以操作子組件中的某個元素
  // 但是函數組件無法接收 ref 屬性 <Child ref={xxx} /> 這樣是不行的
  // 所以就需要用到 forwardRef 進行轉發
  const inputRef = useRef();//{current:''}
  function getFocus(){
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>獲得焦點</button>
      </>
  )
}

useImperativeHandle

  • useImperativeHandle可以讓你在使用 ref 時,自定義暴露給父組件的實例值
  • 在大多數情況下,應當避免使用 ref 這樣的命令式代碼。useImperativeHandle 應當與 forwardRef 一起使用
  • 父組件可以使用操作子組件中的多個 ref
import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';

function Child(props,parentRef){
    // 子組件內部自己創建 ref 
    let focusRef = useRef();
    let inputRef = useRef();
    useImperativeHandle(parentRef,()=>(
      // 這個函數會返回一個對象
      // 該對象會作為父組件 current 屬性的值
      // 通過這種方式,父組件可以使用操作子組件中的多個 ref
        return {
            focusRef,
            inputRef,
            name:'計數器',
            focus(){
                focusRef.current.focus();
            },
            changeText(text){
                inputRef.current.value = text;
            }
        }
    });
    return (
        <>
            <input ref={focusRef}/>
            <input ref={inputRef}/>
        </>
    )

}
Child = forwardRef(Child);
function Parent(){
  const parentRef = useRef();//{current:''}
  function getFocus(){
    parentRef.current.focus();
    // 因為子組件中沒有定義這個屬性,實現了保護,所以這里的代碼無效
    parentRef.current.addNumber(666);
    parentRef.current.changeText('<script>alert(1)</script>');
    console.log(parentRef.current.name);
  }
  return (
      <>
        <ForwardChild ref={parentRef}/>
        <button onClick={getFocus}>獲得焦點</button>
      </>
  )
}

轉載請注明出處原文鏈接地址 Vincent'Blog-React中Refs的使用方法


免責聲明!

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



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