日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

React?Refs?的使用forwardRef?源碼示例解析_React

作者:冴羽 ? 更新時間: 2022-12-07 編程語言

三種使用方式

React 提供了 Refs,幫助我們訪問 DOM 節點或在 render 方法中創建的 React 元素。

React 提供了三種使用 Ref 的方式:

1. String Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
             // 2. 通過 this.refs.xxx 獲取 DOM 節點
             this.refs.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 直接傳入一個字符串
        return (
            <div>
              <input ref="textInput" value='value' />
            </div>
        )
    }
}
root.render(<App />);

2. 回調 Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
              // 2. 通過實例屬性獲取 DOM 節點
              this.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 傳入一個回調函數
        // 該函數中接受 React 組件實例或 DOM 元素作為參數
        // 我們通常會將其存儲到具體的實例屬性(this.textInput)
        return (
            <div>
              <input ref={(element) => {
                this.textInput = element;
              }} value='value' />
            </div>
        )
    }
}
root.render(<App />);

3. createRef

class App extends React.Component {
    constructor(props) {
        super(props)
        // 1. 使用 createRef 創建 Refs
        // 并將 Refs 分配給實例屬性 textInputRef,以便在整個組件中引用
        this.textInputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 3. 通過 Refs 的 current 屬性進行引用
            this.textInputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 通過 ref 屬性附加到 React 元素
        return (
            <div>
              <input ref={this.textInputRef} value='value' />
            </div>
        )
    }
}

這是最被推薦使用的方式。

兩種使用目的

Refs 除了用于獲取具體的 DOM 節點外,也可以獲取 Class 組件的實例,當獲取到實例后,可以調用其中的方法,從而強制執行,比如動畫之類的效果。

我們舉一個獲取組件實例的例子:

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.textInputRef = React.createRef();
    }
    handleFocus() {
        this.textInputRef.current.focus();
    }
    render() {
        return <input ref={this.textInputRef} value='value' />
    }
}
class App extends React.Component {
    constructor(props) {
        super(props)
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
                this.inputRef.current.handleFocus()
        }, 2000)
    }
    render() {
        return (
            <div>
              <Input ref={this.inputRef} value='value' />
            </div>
        )
    }
}

在這個例子中,我們通過 this.inputRef.current 獲取到 Input 組件的實例,并調用了實例的 handleFocus 方法,在這個方法中,又通過 Refs 獲取到具體的 DOM 元素,執行了 focus 原生方法。

Refs 轉發

有的時候,我們開發一個組件,這個組件需要對組件使用者提供一個 ref 屬性,用于讓組件使用者獲取具體的 DOM 元素,我們就需要進行 Refs 轉發,這對于 class 組件并不是一個問題,舉個示例代碼:

class Child extends React.Component {
    render() {
        const {inputRef, ...rest} = this.props;
        // 3. 這里將 props 中的 inputRef 賦給 DOM 元素的 ref
        return <input ref={inputRef} {...rest} placeholder="value" />
    }
}
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 創建 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 使用 this.inputRef.current 獲取子組件中渲染的 DOM 節點
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 因為 ref 屬性不能通過 this.props 獲取,所以這里換了一個屬性名
        return <Child inputRef={this.inputRef} />
    }
}

但對于函數式組件,這卻是一個問題。

我們是不能在函數組件上使用 ref 屬性的,因為函數組件沒有實例。

所以 React 提供了 forwardRef 這個 API,我們直接看使用示例:

// 3. 子組件通過 forwardRef 獲取 ref,并通過 ref 屬性綁定 React 元素
const Child = forwardRef((props, ref) => (
  <input ref={ref} placeholder="value" />
));
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 創建 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 使用 this.inputRef.current 獲取子組件中渲染的 DOM 節點
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 傳給子組件的 ref 屬性
        return <Child ref={this.inputRef} />
    }
}

尤其是在我們編寫高階組件的時候,往往要實現 refs 轉發。我們知道,一個高階組件,會接受一個組件,返回一個包裹后的新組件,從而實現某種功能的增強。

但也正是如此,我們添加 ref,獲取的會是包裹后的新組件的實例,而非被包裹的組件實例,這就可能會導致一些問題。

createRef 源碼

現在我們看下 createRef 的源碼,源碼的位置在 /packages/react/src/ReactCreateRef.js,代碼其實很簡單,就只是返回了一個具有 current 屬性的對象:

// 簡化后
export function createRef() {
  const refObject = {
    current: null,
  };
  return refObject;
}

在渲染的過程中,refObject.current 會被賦予具體的值。

forwardRef 源碼

那 forwardRef 源碼呢?源碼的位置在 /packages/react/src/ReactForwardRef.js,代碼也很簡單:

// 簡化后
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
export function forwardRef(render) {
  const elementType = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
  return elementType;
}

但是要注意這里的 $$typeof,盡管這里是 REACT_FORWARD_REF_TYPE,但最終創建的 React 元素的 $$typeof 依然為 REACT_ELEMENT_TYPE

關于 createElement 的源碼分析參考 《React 之 createElement 源碼解讀》,我們這里簡單分析一下,以 InputComponent 為例:

// 使用 forwardRef
const InputComponent = forwardRef(({value}, ref) => (
  <input ref={ref} className="FancyButton" value={value} />
));
// 根據 forwardRef 的源碼,最終返回的對象格式為:
const InputComponent = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
}
// 使用組件
const result = <InputComponent />
// Bable 將其轉譯為:
const result = React.createElement(InputComponent, null);
// 最終返回的對象為:
const result = {
  $$typeof: REACT_ELEMENT_TYPE,
  type: {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  }
}

我們嘗試著打印一下最終返回的對象,確實也是這樣的結構:

React 系列

React 之 createElement 源碼解讀

React 之元素與組件的區別

原文鏈接:https://juejin.cn/post/7161719602652086308

欄目分類
最近更新