React?Refs?的使用forwardRef?源碼示例解析
三種使用方式
React 提供了 Refs,幫助我們訪問 DOM 節(jié)點(diǎn)或在 render 方法中創(chuàng)建的 React 元素。
React 提供了三種使用 Ref 的方式:
1. String Refs
class App extends React.Component { constructor(props) { super(props) } componentDidMount() { setTimeout(() => { // 2. 通過 this.refs.xxx 獲取 DOM 節(jié)點(diǎn) this.refs.textInput.value = 'new value' }, 2000) } render() { // 1. ref 直接傳入一個字符串 return ( <div> <input ref="textInput" value='value' /> </div> ) } } root.render(<App />);
2. 回調(diào) Refs
class App extends React.Component { constructor(props) { super(props) } componentDidMount() { setTimeout(() => { // 2. 通過實(shí)例屬性獲取 DOM 節(jié)點(diǎn) this.textInput.value = 'new value' }, 2000) } render() { // 1. ref 傳入一個回調(diào)函數(shù) // 該函數(shù)中接受 React 組件實(shí)例或 DOM 元素作為參數(shù) // 我們通常會將其存儲到具體的實(shí)例屬性(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 創(chuàng)建 Refs // 并將 Refs 分配給實(shí)例屬性 textInputRef,以便在整個組件中引用 this.textInputRef = React.createRef(); } componentDidMount() { setTimeout(() => { // 3. 通過 Refs 的 current 屬性進(jìn)行引用 this.textInputRef.current.value = 'new value' }, 2000) } render() { // 2. 通過 ref 屬性附加到 React 元素 return ( <div> <input ref={this.textInputRef} value='value' /> </div> ) } }
這是最被推薦使用的方式。
兩種使用目的
Refs 除了用于獲取具體的 DOM 節(jié)點(diǎn)外,也可以獲取 Class 組件的實(shí)例,當(dāng)獲取到實(shí)例后,可以調(diào)用其中的方法,從而強(qiáng)制執(zhí)行,比如動畫之類的效果。
我們舉一個獲取組件實(shí)例的例子:
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 組件的實(shí)例,并調(diào)用了實(shí)例的 handleFocus 方法,在這個方法中,又通過 Refs 獲取到具體的 DOM 元素,執(zhí)行了 focus 原生方法。
Refs 轉(zhuǎn)發(fā)
有的時候,我們開發(fā)一個組件,這個組件需要對組件使用者提供一個 ref 屬性,用于讓組件使用者獲取具體的 DOM 元素,我們就需要進(jìn)行 Refs 轉(zhuǎn)發(fā),這對于 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. 創(chuàng)建 refs this.inputRef = React.createRef(); } componentDidMount() { setTimeout(() => { // 4. 使用 this.inputRef.current 獲取子組件中渲染的 DOM 節(jié)點(diǎn) this.inputRef.current.value = 'new value' }, 2000) } render() { // 2. 因?yàn)?ref 屬性不能通過 this.props 獲取,所以這里換了一個屬性名 return <Child inputRef={this.inputRef} /> } }
但對于函數(shù)式組件,這卻是一個問題。
我們是不能在函數(shù)組件上使用 ref 屬性的,因?yàn)楹瘮?shù)組件沒有實(shí)例。
所以 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. 創(chuàng)建 refs this.inputRef = React.createRef(); } componentDidMount() { setTimeout(() => { // 4. 使用 this.inputRef.current 獲取子組件中渲染的 DOM 節(jié)點(diǎn) this.inputRef.current.value = 'new value' }, 2000) } render() { // 2. 傳給子組件的 ref 屬性 return <Child ref={this.inputRef} /> } }
尤其是在我們編寫高階組件的時候,往往要實(shí)現(xiàn) refs 轉(zhuǎn)發(fā)。我們知道,一個高階組件,會接受一個組件,返回一個包裹后的新組件,從而實(shí)現(xiàn)某種功能的增強(qiáng)。
但也正是如此,我們添加 ref,獲取的會是包裹后的新組件的實(shí)例,而非被包裹的組件實(shí)例,這就可能會導(dǎo)致一些問題。
createRef 源碼
現(xiàn)在我們看下 createRef
的源碼,源碼的位置在 /packages/react/src/ReactCreateRef.js
,代碼其實(shí)很簡單,就只是返回了一個具有 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
,但最終創(chuàng)建的 React 元素的 $$typeof
依然為 REACT_ELEMENT_TYPE
。
關(guān)于 createElement
的源碼分析參考 《React 之 createElement 源碼解讀》,我們這里簡單分析一下,以 InputComponent
為例:
// 使用 forwardRef const InputComponent = forwardRef(({value}, ref) => ( <input ref={ref} className="FancyButton" value={value} /> )); // 根據(jù) forwardRef 的源碼,最終返回的對象格式為: const InputComponent = { $$typeof: REACT_FORWARD_REF_TYPE, render, } // 使用組件 const result = <InputComponent /> // Bable 將其轉(zhuǎn)譯為: const result = React.createElement(InputComponent, null); // 最終返回的對象為: const result = { $$typeof: REACT_ELEMENT_TYPE, type: { $$typeof: REACT_FORWARD_REF_TYPE, render, } }
我們嘗試著打印一下最終返回的對象,確實(shí)也是這樣的結(jié)構(gòu):
React 系列
以上就是React 之 Refs 的使用和 forwardRef 的源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于React Refs使用forwardRef 的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在?React?中使用?Context?API?實(shí)現(xiàn)跨組件通信的方法
在React中,ContextAPI是一個很有用的特性,可用于組件間的狀態(tài)共享,它允許跨組件傳遞數(shù)據(jù)而無需通過每個組件手動傳遞props,本文給大家介紹在?React?中如何使用?Context?API?來實(shí)現(xiàn)跨組件的通信,感興趣的朋友一起看看吧2024-09-09淺談React組件props默認(rèn)值的設(shè)置
本文主要介紹了淺談React組件props默認(rèn)值的設(shè)置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04React Hooks - useContetx和useReducer的使用實(shí)例詳解
這篇文章主要介紹了React Hooks - useContetx和useReducer的基本使用,本文通過實(shí)例代碼給大家講解的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-11-11react如何利用useRef、forwardRef、useImperativeHandle獲取并處理dom
這篇文章主要介紹了react如何利用useRef、forwardRef、useImperativeHandle獲取并處理dom,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-10-10React實(shí)現(xiàn)歌詞滾動效果(跟隨音樂播放時間滾動)
這篇文章主要為大家詳細(xì)介紹了React實(shí)現(xiàn)歌詞滾動效果(跟隨音樂播放使勁按滾動),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2024-02-02