中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React中的Ref是什么

發布時間:2022-03-22 11:36:48 來源:億速云 閱讀:293 作者:小新 欄目:web開發

這篇文章主要介紹React中的Ref是什么,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

Intro

在 React 項目中,有很多場景需要用到 Ref。例如使用 ref 屬性獲取 DOM 節點,獲取 ClassComponent 對象實例;用 useRef Hook 創建一個 Ref 對象,以便解決像 setInterval 獲取不到最新的 state 的問題;你也可以調用 React.createRef 方法手動創建一個 Ref 對象。

雖然 Ref 用起來也很簡單,但在實際項目中實戰還是難免遇到問題,這篇文章將從源碼的角度出發梳理各種和 Ref 相關的問題,理清和 ref 相關的 API 背后都干了什么。看完這篇文章或許可以讓你對的 Ref 有更深入地認識。

Ref 相關的類型聲明

首先 refreference 的簡稱,也就是引用。在 react 的類型聲明文件中,可以找到好幾個和 Ref 相關的類型,這里將它們一一列舉出來。

RefObject/MutableRefObject

interface RefObject<T> { readonly current: T | null; }
interface MutableRefObject<T> { current: T; }

使用 useRef Hook 的時候返回的就是 RefObject/MutableRefObejct,這兩個類型都是定義了一個 { current: T } 的對象結構,區別是 RefObject 的 current 屬性是只讀的,如果修改 refObject.current,Typescript 會警告??。

const ref = useRef<string>(null)
ref.current = '' // Error

TS 報錯:無法分配到 "current" ,因為它是只讀屬性。

React中的Ref是什么

查看 useRef 方法的定義,這里用了函數重載,當傳入的泛型參數 T 不包含 null 時返回RefObject<T>,當包含 null 時將返回 MutableRefObject<T>

function useRef<T>(initialValue: T): MutableRefObject<T>;
function useRef<T>(initialValue: T | null): RefObject<T>;

所以如果你希望創建的 ref 對象 current 屬性是可修改的,需要加上 | null

const ref = useRef<string | null>(null)
ref.current = '' // OK

調用 React.createRef() 方法時返回的也是一個 RefObject

createRef

export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}

RefObject/MutableRefObject 是在 16.3 版本才新增的,如果使用更早的版本,需要使用 Ref Callback

RefCallback

使用 Ref Callback 就是傳遞一個回調函數,react 回調時會將對應的實例回傳過來,可以自行保存以便調用。這個回調函數的類型就是 RefCallback

type RefCallback<T> = (instance: T | null) => void;

使用 RefCallback 示例:

import React from 'react'

export class CustomTextInput extends React.Component {
  textInput: HTMLInputElement | null = null;

  saveInputRef = (element: HTMLInputElement | null) => {
    this.textInput = element;
  }

  render() {
    return (
      <input type="text" ref={this.saveInputRef} />
    );
  }
}

Ref/LegacyRef

在類型聲明中,還有 Ref/LegacyRef 類型,它們用于泛指 Ref 類型。 LegacyRef 是兼容版本,在之前的老版本 ref 還可以是 字符串。

type Ref<T> = RefCallback<T> | RefObject<T> | null;
type LegacyRef<T> = string | Ref<T>;

理解了和 Ref 相關的類型,寫起 Typescript 來才能更得心應手。

Ref 的傳遞

特殊的 props

在 JSX 組件上使用 ref 時,我們是通過給 ref 屬性設置一個 Ref。我們都知道 jsx 的語法,會被 Babel 等工具編譯成 createElement 的形式。

// jsx
<App ref={ref} id="my-app" ></App>

// compiled to
React.createElement(App, {
  ref: ref,
  id: "my-app"
});

看起來 ref 和其他 prop 沒啥區別,不過如果你嘗試在組件內部打印 props.ref 卻是 undefined。并且 dev 環境控制臺會給出提示。

Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop.

React 對 ref 做了啥?在 ReactElement 源碼中可以看到,refRESERVED_PROPS,同樣有這種待遇的還有 key,它們都會被特殊處理,從 props 中提取出來傳遞給 Element

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

所以 ref 是會被特殊處理的 “props“

forwardRef

16.8.0 版本之前,Function Component 是無狀態的,只會根據傳入的 props render。有了 Hook 之后不僅可以有內部狀態,還可以暴露方法供外部調用(需要借助 forwardRefuseImperativeHandle)。

如果直接對一個 Function Componentref,dev 環境下控制臺會告警,提示你需要用 forwardRef 進行包裹起來。

function Input () {
    return <input />
}

const ref = useRef()
<Input ref={ref} />

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

forwardRef 為何物?查看源碼 ReactForwardRef.js 將 __DEV__ 相關的代碼折疊起來,它只是一個無比簡單的高階組件。接收一個 render 的 FunctionComponent,將它包裹一下定義 $$typeofREACT_FORWARD_REF_TYPEreturn 回去。

React中的Ref是什么

跟蹤代碼,找到 resolveLazyComponentTag,在這里 $$typeof 會被解析成對應的 WorkTag。

React中的Ref是什么

REACT_FORWARD_REF_TYPE 對應的 WorkTag 是 ForwardRef。緊接著 ForwardRef 又會進入 updateForwardRef 的邏輯。

case ForwardRef: {
  child = updateForwardRef(
    null,
    workInProgress,
    Component,
    resolvedProps,
    renderLanes,
  );
  return child;
}

這個方法又會調用 renderWithHooks 方法,并在第五個參數傳入 ref

nextChildren = renderWithHooks(
  current,
  workInProgress,
  render,
  nextProps,
  ref, // 這里
  renderLanes,
);

繼續跟蹤代碼,進入 renderWithHooks 方法,可以看到,ref 會作為 Component 的第二個參數傳遞。到這里我們可以理解被 forwardRef 包裹的 FuncitonComponent 第二個參數 ref 是從哪里來的(對比 ClassComponent contructor 第二個參數是 Context)。

React中的Ref是什么

了解如何傳遞 ref,那下一個問題就是 ref 是如何被賦值的。

ref 的賦值

打斷點(給 ref 賦值一個 RefCallback,在 callback 里面打斷點) 跟蹤到代碼 commitAttachRef,在這個方法里面,會判斷 Fiber 節點的 ref 是 function 還是 RefObject,依據類型處理 instance。如果這個 Fiber 節點是 HostComponent (tag = 5) 也就是 DOM 節點,instance 就是該 DOM 節點;而如果該 Fiber 節點是 ClassComponent (tag = 1),instance 就是該對象實例。

function commitAttachRef(finishedWork) {
  var ref = finishedWork.ref;

  if (ref !== null) {
    var instanceToUse = finishedWork.stateNode;

    if (typeof ref === 'function') {
      ref(instanceToUse);
    } else {
      ref.current = instanceToUse;
    }
  }
}

以上是 HostComponent 和 ClassComponent 中對 ref 的賦值邏輯,對于 ForwardRef 類型的組件走的是另外的代碼,但行為基本是一致的,可以看這里 imperativeHandleEffect。

接下里,我們繼續挖掘 React 源碼,看看 useRef 是如何實現的。

useRef 的內部實現

通過跟蹤代碼,定位到 useRef 運行時的代碼 ReactFiberHooks

React中的Ref是什么

這里有兩個方法,mountRefupdateRef,顧名思義就是對應 Fiber 節點 mountupdate 時對 ref 的操作。

function updateRef<T>(initialValue: T): {|current: T|} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

可以看到 mount 時,useRef 創建了一個 RefObject,并將它賦值給 hookmemoizedStateupdate 時直接將它取出返回。

不同的 Hook memoizedState 保存的內容不一樣,useState 中保存 state 信息, useEffect 中 保存著 effect 對象,useRef 中保存的是 ref 對象...

mountWorkInProgressHookupdateWorkInProgressHook 方法背后是一條 Hooks 的鏈表,在不修改鏈表的情況下,每次 render useRef 都能取回同一個 memoizedState 對象,就這么簡單。

應用:合并 ref

至此,我們了解了在 React 中 ref 的傳遞和賦值邏輯,以及 useRef 相關的源碼。用一個應用題來鞏固以上知識點:有一個 Input 組件,在組件內部需要通過 innerRef HTMLInputElement 來訪問 DOM 節點,同時也允許組件外部 ref 該節點,需要怎么實現?

const Input = forwardRef((props, ref) => {
  const innerRef = useRef<HTMLInputElement>(null)
  return (
    <input {...props} ref={???} />
  )
})

考慮一下上面代碼中的 ??? 應該怎么寫。

============ 答案分割線 ==============

通過了解 Ref 相關的內部實現,很明顯我們這里可以創建一個 RefCallback,在里面對多個 ref 進行賦值就可以了。

export function combineRefs<T = any>(
  refs: Array<MutableRefObject<T | null> | RefCallback<T>>
): React.RefCallback<T> {
  return value => {
    refs.forEach(ref => {
      if (typeof ref === 'function') {
        ref(value);
      } else if (ref !== null) {
        ref.current = value;
      }
    });
  };
}

const Input = forwardRef((props, ref) => {
  const innerRef = useRef<HTMLInputElement>(null)
  return (
    <input {...props} ref={combineRefs(ref, innerRef)} />
  )
})

以上是“React中的Ref是什么”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

巨野县| 景洪市| 张掖市| 扬州市| 吉安市| 屏东县| 邻水| 平泉县| 永平县| 罗山县| 荔波县| 义乌市| 嘉禾县| 同心县| 和林格尔县| 怀化市| 仲巴县| 略阳县| 尼勒克县| 嵊州市| 梁山县| 云南省| 绵竹市| 台山市| 昌邑市| 大姚县| 蕲春县| 财经| 井研县| 阿瓦提县| 科技| 拜泉县| 扎兰屯市| 盱眙县| 杨浦区| 息烽县| 昭觉县| 西平县| 峨眉山市| 轮台县| 阿坝|