亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

React Ref Callback使用場景最佳實踐詳解

 更新時間:2023年01月12日 08:51:10   作者:KooFE  
這篇文章主要為大家介紹了React Ref Callback使用場景最佳實踐詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

本文源于翻譯 React ref Callback Use Cases

在 React 中,"Ref" 具有兩個相關(guān)的含義,而且經(jīng)常讓人困惑。在本文正式開始之前,我們先弄清楚它的定義:

  • 作為 useRef hook 返回的 “ref 對象”:在這種場景中,它就是一個普通的 JavaScript 對象,具有一個名為 current 的屬性,并且可以讀取或設(shè)置為任意值。
  • 作為 JSX DOM 元素上的 “ref 屬性”:用于訪問其對應(yīng)的 DOM 元素

這兩者經(jīng)常一起使用,“ref 對象” 可以傳遞給 “ref 屬性”,React 會將對 DOM 元素設(shè)置為它的 current 屬性值。

ref callback

ref 屬性除了接受 ref 對象之外,還可以接受函數(shù)也就是 ref callback。在該函數(shù)中,DOM 元素作為其唯一參數(shù)。

與 effect 函數(shù)一樣,React 在組件周期中的某些時刻中調(diào)用它。當(dāng)創(chuàng)建 DOM 元素之后會立即執(zhí)行 ref callback(參數(shù)是 DOM 元素),在刪除元素時也會再次調(diào)用 ref callback,只不過這時的參數(shù)是 null。

如果 ref callback 被定義為內(nèi)聯(lián)函數(shù),React 將在每次渲染時調(diào)用它兩次,第一次的參數(shù)是 null,第二次的參數(shù)是 DOM 元素。

雖然內(nèi)聯(lián) ref callback 被調(diào)用兩次可能會令人驚訝,如果從 React 的角度來看,我認為這種行為是合理的。它保證了一致性,因為每次渲染都會創(chuàng)建新的函數(shù)實例,它可能是一個完全不同的函數(shù)。這些函數(shù)可能會依賴 props 或 state,而這些 props 或 state 也可能在此期間發(fā)生了變化。

因此 React 需要清除舊的 ref callback(參數(shù)是 null),然后設(shè)置新的回調(diào)(參數(shù)是 DOM 元素)。這樣我們可以根據(jù)條件來設(shè)置 ref 屬性的值,甚至在 React 元素之間交換它們。

這可能會導(dǎo)致一些不必要的調(diào)用。在大多數(shù)情況下,這不是引起什么問題。如果你不想執(zhí)行這些不必要的調(diào)用,可以通過在 useCallback 中包裝 ref callback或?qū)⒑瘮?shù)移出組件來避免這種行為。

使用場景

在 React Docs 中關(guān)于 ref callback 的內(nèi)容較少。也許是他們故意不去討論它,因為它的使用場景非常少,訪問 DOM 元素的場景并不多見。

ref callback 是 React 的一個小眾功能,你不會每天都需要它。盡管如此,還是有一些場景會用到它,否則,它就不會存在于 React 中了!所以讓我們來看一下在哪些場景會用到它。

需要明確的是,只有在訪問底層的 DOM 元素時,才需要 ref callback。那么,ref callback 何時有用?答案是,當(dāng)您想要在 React 中對 DOM 元素執(zhí)行操作時,請使用 ref callback。據(jù)我所知,這可以歸結(jié)為以下四種情況。

  • 當(dāng)元素掛載或更新時,調(diào)用 DOM 元素上的方法來執(zhí)行一些操作。
  • “通知” DOM 元素的更改,當(dāng) DOM 元素的某些屬性發(fā)生更改時,重新讀取該元素。
  • 將 DOM 元素設(shè)置到 state 中,以便在渲染期間訪問它。
  • 共享 DOM 元素;使用 DOM 元素執(zhí)行多項操作。

現(xiàn)在,讓我們具體來看看每一個場景。

1. DOM 元素掛載并滾動到它所在的位置

您可以在 ref callback 中調(diào)用 DOM 元素上的方法,以執(zhí)行滾動或聚焦等 DOM 操作。例如,自動滾動到列表中的最后一項:

// On first render and on unmount there 
// is no DOM element so `el` will be `null` 
const scrollTo = (el) => {
  if (el) {
    el.scrollIntoView({ behavior: "smooth" });
  }
};
function List({ data }) {
  return (
    <ul>
      {data.map((d, i) => {
        const isLast = i === data.length - 1;
        return (
          <li
            key={d.name}
            // ref callback to scroll to the last list element
            ref={isLast ? scrollTo : undefined}
          >
            {d.name}
          </li>
        );
      })}
    </ul>
  );
}

記住,管理 DOM 是 React 的工作,避免執(zhí)行 DOM 上的可變方法, 比如(insert, remove, set, replace 等),對于 focus 和 scroll 等非破壞性的操作則允許我們開發(fā)實現(xiàn)。

還要注意,瀏覽器在沒有用戶交互的情況下是不允許調(diào)用 DOM 元素的某些方法,如 requestFullscreen。當(dāng)在 ref callback 中調(diào)用時,所有此類受保護的方法都不會執(zhí)行任何操作。

2. 當(dāng) DOM 元素變化時的重新渲染

當(dāng)我們通過 React 訪問某些 DOM 元素屬性時,可以使用 ref callback。當(dāng)我們讀取一個 DOM 元素屬性,比如滾動位置,或者在 ref callback 中調(diào)用一個獲取元素信息的方法,比如 getBoundingClientRect(),并將該信息設(shè)置到 state 中。

測量 DOM 元素

這是一段直接來自(舊)React 文檔的片段:如何測量 DOM 節(jié)點。這是 ref callback的一個很好的例子,所以將其復(fù)制到這里。

const [size, setSize] = useState();
const measureRef = useCallback((node) => {
  setSize(node.getBoundingClientRect());
}, []);
return <div ref={measureRef}>{children}</div>;

在這個案例中,沒有選擇使用 useRef,因為當(dāng) ref 是一個對象時,它并不會把當(dāng)前 ref 值的變化情況通知到我們。使用 callback ref 可以確保即便被測量的節(jié)點在子組件延遲顯示 (比如為了響應(yīng)一次點擊),我們依然能夠在父組件接收到相關(guān)的信息,以便更新測量結(jié)果。注意到我們傳遞了 [] 作為 useCallback 的依賴列表。這確保了 ref callback 不會在再次渲染時改變,因此 React 不會在非必要的時候調(diào)用它。

3. 在 render 中訪問 DOM 元素

如果在 ref 回調(diào)中將 DOM 元素設(shè)置到 state,它將觸發(fā)新的渲染,因為這正是設(shè)置 state 的作用。但是它不會陷入無限渲染循環(huán),因為 setState 是一個穩(wěn)定的函數(shù),因此 ref callback 僅在掛載和卸載時調(diào)用。

在這種情況下,為什么我們不使用 useRef?答案是,因為不允許在渲染過程中訪問 ref 對象。對于渲染中的 DOM 元素,必須通過 state 來訪問。接下來舉幾個例子進行介紹。

React Portal

React portal 主要用于解決組件樹和 DOM 樹的結(jié)構(gòu)之間不一致的問題。portal 將 DOM 樹上不同位置上的組件連接到一起,最為常使用的場景就是將 Modal 彈窗覆蓋整個視窗。

// Assume an empty div with id 'modal' is in your HTML
const modalEl = document.getElementById("modal");
function Modal({ children, ...props }) {
  return ReactDOM.createPortal(
    <ModalBase {...props}>
      {children}
    </ModalBase>,
    modalEl
  );
}

可以使用 document.getElementById() 來獲取 DOM 元素,前提是你能保證它是存在的?;蛟S你不想通過 HTML 來控制 Modal,而是希望能 portal 到一個 React 創(chuàng)建的 DOM 元素上。

這就需要在進行 render 時訪問到相應(yīng)的 DOM 元素,使用 ref callback 可以實現(xiàn)這個功能。

function Parent() {
  const [modalElement, setModalElement] = useState(null);
  return (
    <div>
      <div id="modal-location" ref={setModalElement} />
      {/* Imagine that the modal container and the
          Modal itself are farther apart in the component tree */}
      <Modal modalElement={modalElement}>Warning</Modal>
    </div>
  )
}
function Modal({ children, modalElement, ...props }) {
  return modalElement
    ? ReactDOM.createPortal(
        <ModalBase {...props}>{children}</ModalBase>,
        modalElement
      )
    : null;

在最開始,modalElement 的值是 null,所以需要在創(chuàng)建 portal 之前做一下判斷。

非受控復(fù)合組件

非受控復(fù)合組件(Uncontrolled Compound Components)是一種高級的 React 模式,其核心是 ref callback 來處理 React portal。

復(fù)合組件(Compound Component)是將多個組件組合到一起工作,進而形成一個能夠展示的 UI。復(fù)合組件將復(fù)雜的功能拆分為更小的塊,并且它們在一起共同完成整個復(fù)雜功能。這樣就可以避免產(chǎn)生一個有很多 props 的“上帝組件”。

這種模式與 HTML 元素組合比較類似,比如:

  • <select> 中包含多個 <option>
  • <table> 組件會由 <thead>和 <tbody> 組成
  • <details> 元素中會包含 <summary>

在 React 中,數(shù)據(jù)總是向下流動。當(dāng)數(shù)據(jù)流不符合組件樹的結(jié)構(gòu)時,我們可以通過提升 state 來調(diào)整數(shù)據(jù)流。大多數(shù)時候,這是一個很好的解決方案。

對于一些共享組件,如對話框或側(cè)邊欄,頁面上只能有一個,提升 state 會使這些 state “過于全局”。這些 state 將通過許多中間組件連接起來,而這些中間組件實際上并不需要知道它。它會污染整個鏈條上的組件,并會使代碼變得混亂。

我們可以把 React state 看作是懸掛在組件樹上的繩子。繩子的長度代表了定義 state 的組件到使用 state 的 UI 之間的距離。所以,當(dāng)你的繩子長度越長、數(shù)量越多時,它們就越容易被纏在一起。所以要盡量縮短繩子的長度,同時控制繩子的數(shù)量。

出現(xiàn)這個問題的根源是,我們強行將組件樹和數(shù)據(jù)流適配成 DOM 樹的形狀。反過來,我們可以通過組件樹適應(yīng)數(shù)據(jù)流來反轉(zhuǎn)控制。使用 Portal,我們可以重置組件間的距離,并保持 DOM 結(jié)構(gòu)不變。這樣就可以將拉近相關(guān)組件的距離,即便是在 DOM 樹中的離得很遠。這使我們的 React 代碼更容易理解。

非受控復(fù)合組件的實現(xiàn)過程:

  • ref callback定位到元素位置。獲取 DOM 元素并將其置于 state 中。這里我們不能使用 ref 對象,因為我們需要在 render 中使用它,而且要在設(shè)置它的時候觸發(fā)更新。
  • 用 Context 共享元素位置。將這個 DOM 元素放存放在 context 中,以便 context 中的所有組件都可以訪問 DOM 中的這個位置。
  • 使用 Portal 連接到該位置。從 context 中獲取對 DOM元素,并將組件進行 portal。

如果你在想 “這一切聽起來很復(fù)雜”,是的,你沒有錯。這是一種高級模式,需要一些額外的成本,作為回報 -- 它能夠讓你編寫了更簡單的組件。這是值得的(IMO)。也許你會發(fā)現(xiàn),你并不經(jīng)常需要它,但是一旦有需要時,就會體驗到其中的樂趣。“Amazing”!

比如在下面的例子中實現(xiàn)一個復(fù)雜的面包屑:

每個 <Breadcrumb/> 都會 portal 到 <BreadcrumbPortal/> 中的 breadcrumbElement 元素

<BreadcrumbPortal/> 會按它們的渲染順序在 <Breadcrumbs/> 展示出來

如果 <BreadcrumbPortal/> 沒有渲染,這時 breadcrumbElement 為 null,<Breadcrumb/> 也不會渲染

4. 共享 DOM Ref

經(jīng)常會出現(xiàn)不止一個消費者需要訪問 DOM 元素。假設(shè)你想測量一個 <div>的寬度,并將其交給另一個 React 之外的庫來處理 DOM 內(nèi)容。對于 React 來說,這樣的元素就是一個黑盒。React 既不知道它的內(nèi)部有什么,也不關(guān)心它是什么。這個元素完全交給另外一個庫來管理。

一個典型的例子就是使用 D3 或 @observable/plot 創(chuàng)建的響應(yīng)式圖標。在下面的例子中,我們會使用 @observable/plot 創(chuàng)建一個 plot,并且使用 react-use-measure 來計算元素的寬度。使用 ref callback將 DOM 元素傳遞給它們倆:

import useMeasure from "react-use-measure";
import * as Plot from "@observablehq/plot";
export function BoxPlot({ data }) {
  const [measureRef, { width, height }] = useMeasure({ debounce: 5 });
  const plotRef = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    const boxPlot = Plot.plot({
      width: Math.max(150, width),
      marks: [
        Plot.boxX(data),
      ],
    });
    plotRef.current.append(boxPlot);
    return () => boxPlot.remove();
  }, [data, width]);
  const initBoxPlot = useCallback((el: HTMLDivElement | null) => {
    plotRef.current = el;
    measureRef(el);
  }, []);
  return <div ref={initBoxPlot} />;
}

總結(jié)

  • ref callback是一個傳遞給元素的 ref 屬性的函數(shù)。React 會在組件掛載時調(diào)用它,這時的參數(shù)是 DOM 元素;當(dāng)組件卸載的時候也會調(diào)用它,這時的參數(shù)是 null。
  • 當(dāng)你設(shè)置不同的 ref callback時,React 也會調(diào)用 ref callback
  • 切換綁定和取消綁定 ref 到一個 DOM 元素,ref callback可以讓我們實現(xiàn)特定的操作。

ref callback可以用來做以下事情

  • 操作 DOM,比如在組件掛載的時候滾動或聚焦
  • 在 React 獲取 DOM 屬性,比如寬度或滾動位置
  • 在 React 控制的 DOM 元素上使用 Portal

非受控復(fù)合組件是一個很強大的模式,使用了 Portal

將 DOM 元素提供給多個消費者

以上就是React Ref Callback最佳實踐詳解的詳細內(nèi)容,更多關(guān)于React Ref Callback實踐的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React 封裝自定義組件的操作方法

    React 封裝自定義組件的操作方法

    React中自定義組件的重要性在于它們提供了代碼重用、降低耦合性、提升可維護性、更好的團隊協(xié)作、靈活性和易于測試和調(diào)試等好處,從而提高了開發(fā)效率和質(zhì)量,這篇文章主要介紹了React 封裝自定義組件,需要的朋友可以參考下
    2023-12-12
  • React?組件傳?children?的各種案例方案詳解

    React?組件傳?children?的各種案例方案詳解

    自定義組件的時候往往需要傳?children,由于寫法比較多樣,我就總結(jié)了一下,要自定義的組件其中包含一個?title?和一個?children,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧
    2023-10-10
  • 對react中間件的理解

    對react中間件的理解

    這篇文章主要介紹了對react中間件的理解,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • React表單容器的通用解決方案

    React表單容器的通用解決方案

    本文主要介紹了React表單容器的通用解決方案,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 解決React報錯Functions are not valid as a React child

    解決React報錯Functions are not valid as 

    這篇文章主要為大家介紹了React報錯Functions are not valid as a React child解決詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • React Native中ScrollView組件輪播圖與ListView渲染列表組件用法實例分析

    React Native中ScrollView組件輪播圖與ListView渲染列表組件用法實例分析

    這篇文章主要介紹了React Native中ScrollView組件輪播圖與ListView渲染列表組件用法,結(jié)合實例形式詳細分析了ScrollView組件輪播圖與ListView渲染列表組件具體功能、使用方法與操作注意事項,需要的朋友可以參考下
    2020-01-01
  • React?懸浮框內(nèi)容懶加載實例詳解

    React?懸浮框內(nèi)容懶加載實例詳解

    這篇文章主要為大家介紹了React?懸浮框內(nèi)容懶加載實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • 你知道怎么在 HTML 頁面中使用 React嗎

    你知道怎么在 HTML 頁面中使用 React嗎

    這篇文章主要為大家詳細介紹了如何在HTML頁面中使用 React,使用使用js模塊化的方式開發(fā),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • React Umi國際化配置方法

    React Umi國際化配置方法

    這篇文章主要介紹了React Umi國際化配置方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-04-04
  • React 組件間的通信示例

    React 組件間的通信示例

    這篇文章主要介紹了React 組件間的通信示例,主要通信劃分為三種,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06

最新評論