react中useRef的使用和渲染機制
前言
剛開始使用react時,由于對react的hook不太了解,導(dǎo)致在使用useState時,出現(xiàn)了閉包的問題,當(dāng)時搜索解決方法時,發(fā)現(xiàn)了useRef這個hook可以很快的解決這個問題。這里用來記錄下自己對useRef這個hook的理解。
useRef的渲染機制
先要了解react中useRef和useState的區(qū)別,useState是用來管理組件狀態(tài)的,而useRef是用來管理組件引用的。useState會導(dǎo)致組件重新渲染,而useRef不會。對應(yīng)上面說的useRef解決閉包問題,其實不是react設(shè)置該hook的初衷,useRef這個hook的初衷是用來解決DOM操作問題的。能夠解決閉包問題也只是其副作用之一。對于useRef的渲染機制我們可以總結(jié)以下幾個關(guān)鍵點:
- useRef在組件首次渲染時創(chuàng)建一個對象 { current: initialValue }
- 整個組件的生命周期,不會創(chuàng)建新對象,返回的都是首次創(chuàng)建對象的引用
- 無論如何賦值,都不會導(dǎo)致組件重新渲染(React通過Object.is比較檢測不到變化,因此不會觸發(fā)渲染)
以下是基于useRef的渲染機制的代碼示例,組件使用了antd的Button和Card組件,從代碼運行中我們可以看出,點擊更新Ref值按鈕,組件沒有重新渲染,但是點擊更新State值按鈕,組件會重新渲染。

import { Button, Card } from "antd";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
const DemoRef = () => {
const ref = useRef<any>(null)
// 渲染次數(shù)
const renderCountRef = useRef(1);
const renderValueRef = useRef<any>(0);
const [renderValue, setRenderValue] = useState<any>(0);
useEffect(() => {
renderCountRef.current = renderCountRef.current + 1;
console.log(`?? 組件第 ${renderCountRef.current + 1} 次渲染`);
});
return <div className="flex w-full">
<Card title="useRef的渲染機制" className="ml-12px" hoverable={true}>
<div className="mt-12px max-w-200px text-[#e74c3c] bg-[#fffacd] p-4 text-[18px] font-bold flex w-full flex-row w-400px">
組件渲染此時:<span className="font-bold">{renderCountRef.current}</span>
</div>
<Button onClick={() => {
renderValueRef.current = renderCountRef.current + 1;
}} className="mt-12px">更新Ref值</Button>
<div className="mt-12px font-bold mb-12px">當(dāng)前Ref值:{renderValueRef.current}(點擊雖然新增了,但是組件沒有重新渲染,導(dǎo)致此處仍然時老的值)</div>
<Button onClick={() => {
setRenderValue((pre: number) => pre + 1);
}}>更新State值</Button>
<div className="mt-12px font-bold">當(dāng)前State值:{renderValue}(點擊會觸發(fā)組件重新渲染,導(dǎo)致此處的值會更新)</div>
</Card>
</div>
}
useRef解決的問題
解決閉包問題:useRef能夠在閉包函數(shù)中訪問到最新的狀態(tài)或?qū)傩允且驗?current屬性的引用不會改變。實際編碼中以下兩個場景會產(chǎn)生閉包,計時器顯示和事件函數(shù)監(jiān)聽,下面分享下useRef在這兩種場景的應(yīng)用
- 計時器中使用最新的狀態(tài)或?qū)傩?/li>
const [duration, setDuration] = useState(0); const durationRef = useRef(duration); useEffect(() => { const interval = setInterval(() => { durationRef.current = durationRef.current + 1; setDuration(durationRef.current); }, 1000); return () => clearInterval(interval); }, []);- 事件處理函數(shù)中使用最新的狀態(tài)或?qū)傩?,此處不再列舉代碼和說明,因為和計時器的場景類似。
react組件中DOM操作:useRef可以用來操作DOM元素,例如獲取輸入框的值、滾動到指定位置等。 const inputRef = useRef(null); const handleClick = () => { inputRef.current.focus(); };
解決性能問題,方便避免重復(fù)創(chuàng)建ref的內(nèi)容
useRef的好兄弟forwardRef
在日常的開發(fā)中,多層組件嵌套是常有的場景,例如父組件中嵌套子組件,子組件中又嵌套孫子組件等。在這種場景下,我們偶爾會需要在父組件中操作子組件的DOM。這時候,如果直接在子組件上使用useRef,會獲取不到子組件的DOM并且控制還會報錯,原因是react為了保證組件的封裝性,默認(rèn)情況下自定義的組件是不會暴漏其內(nèi)部DOM節(jié)點的ref,具體錯誤大家可以自己試試。報錯提示中會提示我們在子組件中需要使用forwardRef來轉(zhuǎn)發(fā)ref。
- 以下是基于forwardRef的代碼示例,從代碼運行中我們可以看出,點擊設(shè)置子組件的年齡為18按鈕,子組件的年齡會更新為18。
- 使用方式:子組件使用forwardRef包裹,需要轉(zhuǎn)發(fā)的方式使用useImperativeHandle
import { Button, Card } from "antd";
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
const LoginForm = forwardRef((props: any, ref: React.ForwardedRef<{ reset: (flag: boolean) => void; }>) => {
const { name } = props;
const [formData, setFormData] = useState({
username: '',
password: ''
});
useImperativeHandle(ref, () => ({
// 重置表單內(nèi)容
reset: () => {
setFormData({
username: '',
password: ''
})
},
}));
return <div className="mt-12px font-bold">
<h2>{name}</h2>
<div>
<div className="mt-12px">
用戶名:<Input type="text" value={formData.username} onChange={(e) => {
setFormData({
...formData,
username: e.target.value
})
}} />
</div>
<div className="mt-12px">
密碼:<Input type="password" value={formData.password} onChange={(e) => {
setFormData({
...formData,
password: e.target.value
})
}} />
</div>
</div>
</div>
})
//父組件
<Card title="useRef和useForwardRef的組合" className="ml-12px" hoverable={true}>
<Button onClick={() => {
childRef.current?.reset();
}} type="primary">重置</Button>
<Divider></Divider>
<LoginForm name="登錄表單" ref={childRef} />
</Card>
- forwardsRef注意點
- forwardsRef和useRef組合很方便操作子組件的DOM,但是我們盡量避免在父組件中直接操作子組件的DOM,因為這會破壞組件的封裝性,導(dǎo)致代碼難以維護(hù)。
- forwardsRef和useRef組合通過useImperativeHandle轉(zhuǎn)發(fā)的方法,我們可以在父組件中控制子組件的狀態(tài),但是如非必要此種也盡量少用,優(yōu)先用 useState + props 傳遞狀態(tài)(如父組件通過 isVisible props 控制子組件彈窗),而非用 ref 調(diào)用方法(ref 僅用于 “必須操作 DOM / 內(nèi)部方法” 的場景)
到此這篇關(guān)于react中useRef的使用和渲染機制的文章就介紹到這了,更多相關(guān)react useRef使用和渲染內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react組件從搭建腳手架到在npm發(fā)布的步驟實現(xiàn)
這篇文章主要介紹了react組件從搭建腳手架到在npm發(fā)布的步驟實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01
React實現(xiàn)Excel文件的導(dǎo)出與在線預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹了如何利用?React?18?的強大功能,演示如何使用?React?18?編寫?Excel?文件的導(dǎo)出與在線預(yù)覽功能,需要的小伙伴可以參考下2023-12-12
react-router?重新加回跳轉(zhuǎn)攔截功能詳解
這篇文章主要為大家介紹了react-router?重新加回跳轉(zhuǎn)攔截功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
React模仿網(wǎng)易云音樂實現(xiàn)一個音樂項目詳解流程
這篇文章主要介紹了React模仿網(wǎng)易云音樂實現(xiàn)一個音樂項目的詳細(xì)流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
詳解對于React結(jié)合Antd的Form組件實現(xiàn)登錄功能
這篇文章主要介紹了詳解對于React結(jié)合Antd的Form組件實現(xiàn)登錄功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
詳解React-Native全球化多語言切換工具庫react-native-i18n
這篇文章主要介紹了詳解React-Native全球化語言切換工具庫react-native-i18n,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11

