【React Hooks原理 - useRef】

概述

在Function Component項目中當我們需要操作dom的時候,第一時間想到的就是使用useRef這個Hook來綁定dom。但是這個僅僅是使用這個Hook而已,為了更好的學習React Hooks內部實現原理,知其所以然。所以本文根據源碼從useRef的基礎使用場景一步一步到內部實現來對其進行介紹。

基本使用

在React中useRef是這樣定義的:useRef保存一個可變的持久化引用,重新渲染時不會重值,更新值也不會渲染頁面

export function useRef<T>(initialValue: T): { current: T } {const dispatcher = resolveDispatcher();return dispatcher.useRef(initialValue);
}

由代碼能看出useRef接收任意類型的值,包含普通值、函數、dom,然后經過dispather進行派發處理,返回一個包含current屬性的對象引用,該對象和普通Js對象一致,更新不收React約束。

一般在項目中useRef常用的有兩個使用場景:

  • 通過useRef保持持久化的值,且不需要重新渲染
  • 通過useRef綁定dom,以便直接進行dom操作

比如在項目中常用的定時器,我們都會在組件銷毀時通過clear函數進行定時器的清除避免內存泄露等問題,這時候就可以通過useRef來綁定timerId

import { useRef } from 'react';let timerId = useRef(null);useEffect(() => {timerId.current = setInterval(() => {console.log('setInterval');}, 1000);return () => {clearInterval(timerId.current);}
}, [])export default function Counter() {return <></>
}

當我們需要進行dom操作時,比如獲取焦點、自動滾動等,就可以通過useRef來綁定dom進行操作

import { useRef } from 'react';let inputRef = useRef(null);useEffect(() => {// 在組件掛載后聚焦輸入框inputRef.current.focus();
}, [])export default function Counter() {return <input ref={inputRef} type='text' />
}

源碼解析

由于這里的mount、update邏輯很簡單,并當useRef傳遞值/函數和傳遞dom時的處理是不一樣的,所以我們以此來分開介紹。

傳遞普通值時

當傳遞普通值時(包含任意類型值、函數),主要執行mountRef、updateRef兩個函數。在mount掛載時創建一個包含current屬性的對象,然后在更新時返回相同的引用memoizedState保存的,所以這里就在一起寫了。

function mountRef<T>(initialValue: T): { current: T } {// 創建hook鏈表const hook = mountWorkInProgressHook();// ref初始化const ref = { current: initialValue };hook.memoizedState = ref;// 返回refreturn ref;
}function updateRef<T>(initialValue: T): { current: T } {// 復用hookconst hook = updateWorkInProgressHook();// 返回相同引用return hook.memoizedState;
}

從源碼能看出,useRef接收一個初始化參數,可以為值/返回值的函數,然后在mountRef中創建了一個包含current的對象,在updateRef中仍然返回的該對象引用。

如果初始值是函數,因為React內部不會做判斷,直接將初始值賦予current,如何是函數,則需要手動顯式調用

由于不管在mount掛載時,還是在update更新時都是返回的對象引用,以此來保持持久化,當我們通過ref.current修改值時本質修改的是同一個引用對象,所以也不會觸發重新渲染(object.is對比一直都是true)。

傳遞DOM時

當傳遞DOM時,在mount、update階段也和傳值一樣,不會做任何處理會返回相應的對象引用,但是如果傳遞的是DOM時,在Reconciler協調器中通過React.createElement將JSX轉換為React元素后進行fiber構造,在構造完成生產fiber樹之后會進入到commit階段,在該階段會遍歷節點對副作用和ref進行處理,其中在layout階段會判斷當前節點類型(tag)如何是dom(tag === HostComponent)時,如果該dom有ref,則會對ref進行處理commitAttachRef函數

在commit階段,即renderer階段,針對dom的不同狀態和處理分為了三個階段: Before Mutation、Mutation、Layout。有興趣的可以查看這篇文章【React源碼 - Fiber架構之Renderer】

以下commitAttachRef代碼(省略了部分代碼):

function commitAttachRef(finishedWork: Fiber) {// 獲取節點的ref屬性const ref = finishedWork.ref;if (ref !== null) {// 獲取dom實例,fiber.stateNode就是綁定的dom,在completeWork中會創建dom然后綁定到fiber.stateNode上const instance = finishedWork.stateNode;let instanceToUse;switch (finishedWork.tag) {case HostHoistable:case HostSingleton:case HostComponent:// 獲取dom實例instanceToUse = getPublicInstance(instance);break;default:instanceToUse = instance;}//if (typeof ref === "function") {// 將dom實例回傳給傳遞的ref函數finishedWork.refCleanup = ref(instanceToUse);} else {// 普通對象賦值到currentref.current = instanceToUse;}}
}

從代碼能看出該函數主要就是獲取ref綁定的dom實例,然后根據傳入ref的不同進行處理,如果是函數則將dom實例傳遞給函數由開發者顯式調用,否則則綁定到current屬性上進行返回。

傳遞函數,顯式處理ref的demo:

import React, { useEffect, useRef } from 'react';function App() {const divRef = useRef(null);useEffect(() => {if (divRef.current) {console.log('Element mounted:', divRef.current);}return () => {console.log('Element unmounted:', divRef.current);};}, []);return <div ref={divRef}>Hello, World!</div>;
}export default App;

總結

基于以上了解,我們知道了useRef的基礎使用和場景以及背后的代碼處理,簡要總結一下就是:useRef用于持久化引用,返回普通Js引用,修改其值不會導致組件重新渲染。當傳遞普通值時,不會進行特殊處理,只是返回相同的對象引用。當綁定dom時,在mount、update階段初始化對象,然后在commit階段進行ref處理,函數顯式處理則會將dom實例作為參數回傳,普通值則會綁定到ref.current中

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45361.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45361.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45361.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用shell腳本打印99乘法表

一、簡介 前一段時間在舊電腦上安裝 antiX 23.1 操作系統&#xff0c;遇到一些問題需要使用shell腳本解決問題&#xff0c;所以專門學習了幾天&#xff0c;打印99乘法表是其中的一個練習作業。 二、學習Linux可行的幾種方式 虛擬機安裝Linux進行學習直接雙系統安裝在實體電腦…

Ubuntu新系統的使用

1.安裝顯卡驅動 直接到軟件與更新里面&#xff0c;就是一個A字圖標的那個軟件打開&#xff0c;到附加驅動里選擇。要選擇“server driver”的&#xff0c;選擇后確認即可。 然后輸入&#xff1a;nvidia-sim查看 別的方法太復雜&#xff0c;這個方法我親測了兩臺電腦&#xff…

kubebuilder入門

1. 安裝kubebuilder brew install kubebuilder 2. 需求描述 開發一個zk operator。 cr定義為ZooKeeperCluster 3. 開發過程 3.1 創建一個空的文件夾zk-operator mkdir zk-operator 3.2 進入該文件夾 cd zk-operator 3.3 執行初始化 kubebuilder init --domain my.doma…

MWA(Modern Web App)初學那些事-2-Basic HTML CSS

初學MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS 目錄 初學MWA(Modern Web App&#xff09;那些事-2-Basic HTML & CSS前言一、本節學習目標二、HTML基礎內容2.1關鍵元素2.4 Scripts 三、CSS 基礎內容3.1 級聯樣式表-用于設置網頁樣式和布局3.2 CSS規則語…

springcloud使用微服務的搭建

微服務的搭建 1.配置對應信息 Springboot 、springcloud、springcloud alibaba對應關系 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E 2.pom.xml的配置 2.1 總項目pom.xml引入依賴 <parent><groupId>org.sprin…

阿里通義音頻生成大模型 FunAudioLLM 開源

簡介 近年來&#xff0c;人工智能&#xff08;AI&#xff09;技術的進步極大地改變了人類與機器的互動方式&#xff0c;特別是在語音處理領域。阿里巴巴通義實驗室最近開源了一個名為FunAudioLLM的語音大模型項目&#xff0c;旨在促進人類與大型語言模型&#xff08;LLMs&…

vue3在 setup 中訪問路由和當前路由

報錯信息&#xff1a; Cannot read properties of undefined (reading $router) 原因&#xff1a; 因為我們在 setup 里面沒有訪問 this&#xff0c;所以我們不能直接訪問 this.$router 或 this.$route。 解決方案&#xff1a; 作為替代&#xff0c;我們使用 useRouter 和…

Oracle字符集修改

提示 Oracle數據庫默認的字符集編碼為US7ASCII&#xff0c;這個編碼是不支持中文的&#xff0c;如果想要在數據庫存儲中文&#xff0c;就需要修改編碼為ZHS16GBK或UTF-8 編碼和字符集是一個意思&#xff0c;只是叫法不一樣而已 前置條件 修改字符集的前提是知道我們現在用的是什…

跳妹兒學編程之ScratchJr(9):程序控制積木篇—短跑比賽

跳妹兒學編程之ScratchJr(7)&#xff1a;動作積木篇—爸爸去散步 跳妹兒學編程之ScratchJr(8)&#xff1a;外觀積木篇—捉迷藏 跳妹兒學編程之ScratchJr(9)&#xff1a;程序控制積木篇—短跑比賽 引言 在之前的一篇文章中&#xff0c;我們了解了ScratchJr的動作積木和外觀積…

std::getline

std::getline 是 C 標準庫中的一個函數&#xff0c;用于從輸入流中讀取一行數據并存儲到字符串中。它通常用于讀取用戶輸入或從文件中讀取文本數據。以下是 std::getline 的一般用法和說明&#xff1a; #include <iostream> #include <string>int main() {std::st…

skywalking 請求鏈路采樣設置和原理

目標 skywalking 默認情況會采集大量 trace 數據&#xff0c;這樣可以比較全的追蹤所有請求調用鏈路的請求&#xff0c;但同時對 ES 存儲資源要求非常高&#xff0c;需要我們投入很大的存儲節點才可以。那么有沒有一種采樣的請求上報的機制呢&#xff1f;答案是有的&#xff0…

阿里云ECS服務器安裝jdk并運行jar包,訪問成功詳解

安裝 OpenJDK 8 使用 yum 包管理器安裝 OpenJDK 8 sudo yum install -y java-1.8.0-openjdk-devel 驗證安裝 安裝完成后&#xff0c;驗證 JDK 是否安裝成功&#xff1a; java -version設置 JAVA_HOME 環境變量&#xff1a; 為了確保系統中的其他應用程序可以找到 JDK&…

星火智能體創建指南,星火大模型智能體創建教程

一、什么是星火助手 星火助手是基于訊飛星火認知大模型&#xff0c;面向用戶使用場景&#xff0c;打造的高效生產力工具。通過設置結構化的指令模板&#xff0c;用戶即可完成助手功能設定&#xff0c;每個助手在對話的模式下能夠快速滿足場景需求。同時支持助手模板、數據集、…

Spring boot 2.0 升級到 3.3.1 的相關問題 (一)

文章目錄 Spring boot 2.0 升級到 3.3.1 的相關問題 &#xff08;一&#xff09;攔截器Interceptor的變動問題介紹解決方案 WebMvcConfigurerAdapter 自定義Mvc配置問題介紹解決方案 Spring boot 2.0 升級到 3.3.1 的相關問題 &#xff08;一&#xff09; 攔截器Interceptor的…

單鏈表算法 - 鏈表的中間節點

. - 力扣&#xff08;LeetCode&#xff09;. - 備戰技術面試&#xff1f;力扣提供海量技術面試資源&#xff0c;幫助你高效提升編程技能,輕松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/middle-of-the-linked-list/description/ 思路1: 思路2: 代碼: /*** …

【接口自動化_06課_Pytest+Excel+Allure完整框架集成】

一、logging在接口自動化里的應用 1、設置日志的配置&#xff0c;并收集日志文件 日志的設置需要在pytest.ini文件里設置。這個里面盡量不要有中文 2、debug日志的打印 pytest.ini文件的開關一定得是true才能在控制臺打印日志 import allure import pytest from P06_PytestFr…

CUDA cuDNN和pytorch(GPU版)的完整安裝教程

? * 說明: 本教程使用wsl-ubuntu20.04, 其他發行版linux的命令可能有所區別. *實測機型: i5-13500HX | RTX 4060 Laptop 一、下載CUDA12.X版本 這里以下載CUDA12.2為例。 前往cuda-12.2下載頁, 按照如圖方式選擇合適的選項&#xff1a; 按照官方給出的命令&#xff0c; 在b…

Trie樹的應用

Trie樹的應用 題目解題思路代碼 題目 維護一個字符串集合&#xff0c;支持兩種操作&#xff1a; I x 向集合中插入一個字符串 x x x&#xff1b;Q x 詢問一個字符串在集合中出現了多少次。 共有 N N N 個操作&#xff0c;所有輸入的字符串總長度不超過 1 0 5 10^5 105&am…

ArkTS學習筆記_封裝復用之@builderParam裝飾器

ArkTS學習筆記_封裝復用之builderParam裝飾器 作用&#xff1a; 在自定義組件中&#xff0c;該裝飾器用于裝飾函數成員變量&#xff0c;builderParam裝飾的函數成員變量的值必須是經過builder裝飾的方法。變量初始化后可以在自定義組件內調用。初始化&#xff1a; 可以使用自定…

移動應用性能關注分析哪些指標

移動應用常見性能指標 要對應用開展性能測試&#xff0c;首先需要了解需要重點關注哪些指標&#xff1f;指標的參考范圍大致是多少&#xff1f;可采用哪些工具收集這些指標&#xff1f;如何收集&#xff1f;如果指標有異常&#xff0c;大致有哪些high level的優化思路。這篇博客…