前言 接下來要來聊一個有趣的 Hook,也就是 useRef 這個 Hook,這個 Hook 有什麼特別的功能呢?就讓我們瞧瞧吧!
useRef 前面這邊先提一下 useRef 這個 Hook 稍微有一點特別而且有趣,為什麼會特別且有趣呢?這邊先讓我們看一下 useRef 基本寫法
1 const ref = useRef (initialValue);
基本上 useRef 用法與前面的 Hook 都差不多,但是要稍微注意的事情是 useRef 會回傳一個物件,而這個物件會有一個 current 的屬性,這個 current 屬性就是我們要存放的值,這邊先來看一個簡單的範例
1 2 3 4 5 6 7 8 9 10 11 12 const App = ( ) => { const ref = React .useRef (0 ); console .log (ref); return ( <div > </div > ); }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
很有趣吧?useRef 明明傳入的是一個單純的 Number 0,但是卻回傳一個 { current: 0 } 物件,那麼這是為什麼呢?這邊讓我們翻一下 React Hook 原始碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function useRef<T>(initialValue : T): {current : T} { currentlyRenderingComponent = resolveCurrentlyRenderingComponent (); workInProgressHook = createWorkInProgressHook (); const previousRef = workInProgressHook.memoizedState ; if (previousRef === null ) { const ref = {current : initialValue}; if (__DEV__) { Object .seal (ref); } workInProgressHook.memoizedState = ref; return ref; } else { return previousRef; } }
上面這一段稍微可能有一點複雜,所以我們先稍微精簡一下變成以下這樣
1 2 3 4 5 6 7 8 9 10 function useRef<T>(initialValue : T): {current : T} { if (previousRef === null ) { const ref = {current : initialValue}; return ref; } else { } }
我們可以看到回傳物件的原因就是 React 處理的,因此這也是為什麼 useRef 會回傳一個物件。
除此之外 useRef 並不會觸發 re-render,因此可以拿來存放一些不會變動的值,但是你要注意一下,請不要這樣子撰寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const App = ( ) => { let ref = React .useRef (0 ); React .useEffect (() => { ref += 1 ; }, []); return ( <div > </div > ); }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
如果你如上方這樣子撰寫的話,其實你 console.log(ref) 之後會看到 [object Object]1,這樣子是錯誤的,因為 ref 是一個物件,所以你要這樣子撰寫才正確
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const App = ( ) => { let ref = React .useRef (0 ); React .useEffect (() => { ref.current += 1 ; console .log (ref); }, []); return ( <div > </div > ); }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
那麼這邊拉回到剛剛前面所提的「useRef 並不會觸發 re-render」這件事情,我們是如何知道畫面不會被重新 re-render 呢?其實驗證方式很簡單,將 ref 渲染在畫面上,然後寫個 setTimeout 去累加就可以知道畫面會不會 re-render
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const App = ( ) => { const ref = React .useRef (0 ); React .useEffect (() => { setTimeout (() => { ref.current += 1 ; }, 2000 ) }, []); return ( <div > { ref.current } </div > ); }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
我們可以看到上述範例中,畫面並沒有被重新 re-render,因此我們可以確定 useRef 並不會觸發 re-render。
那麼因為這個特性關係,因此我們可以藉由此特性來監測畫面是否有被 re-render,而這邊我們必須搭配 useEffect 來使用,因為 useEffect 會在每次 re-render 後被觸發,所以這邊舉例一範例來說明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const App = ( ) => { const [count, setCount] = React .useState (0 ); const ref = React .useRef (0 ); const fn =( ) => { setCount ((pre ) => pre + 1 ); } const showRef = ( ) => { console .log (`當前畫面以 re-render ${ ref.current} 次` ); } React .useEffect (() => { ref.current += 1 ; }); return ( <div > <p > count:{ count }</p > <button type ="button" className ="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick ={ fn }> 點我</button > | <button type ="button" className ="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick ={ showRef }> 顯示已渲染的次數</button > </div > ) }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
我們可以看到上述範例中,當我們點擊「點我」按鈕時,畫面上的 count:0 會被重新 re-render 成 count:1,隨著我們點擊幾次,畫面上就會跟著呈現相對應得 count,而當我們點擊「顯示已渲染的次數」按鈕時,我們可以看到畫面已經被重新 re-render 了 N 次,因為 useEffect 會在每次 re-render 後被觸發,所以這邊我們可以藉由 useRef 來監測畫面是否有被 re-render,但你會發現不管怎麼樣 red.current 永遠都是比當前畫面的 count 多一次,這原因主要是發生在初始 React 所導致的。
已經看到大半篇的 useRef 介紹,但實質上來講感覺 useRef 好像在開發上很弱、很沒用對吧?所以這邊我們就來插個花,先來聊一下 Vue 的部分。
還記得你在初學 Vue 的時候都是如何選取 DOM 的嗎?忘了也沒關係,這邊提供範例程式碼讓你回憶一下
1 2 3 <div id ="app" > <p class ="p" > Lorem ipsum dolor, sit amet consectetur adipisicing elit. Iure commodi consectetur cumque nesciunt dolorem deleniti ipsum atque modi libero consequuntur, porro labore autem quod! Voluptatibus eos eveniet optio nemo atque.</p > </div >
1 2 3 4 5 6 7 8 9 10 11 const { createApp, onMounted } = Vue ;const app = createApp ({ setup ( ) { onMounted (() => { console .log ('DOM:' , document .querySelector ('.p' )); }) } }); app.mount ('#app' );
上面就是一個很簡單的 Vue 選取 DOM 方式。
但這種方式並不是很方便畢竟要寫很長的 document.querySelector,所以 Vue 也提供了 ref 來幫助我們選取 DOM
1 2 3 <div id ="app" > <p ref ="p" > Lorem ipsum dolor, sit amet consectetur adipisicing elit. Iure commodi consectetur cumque nesciunt dolorem deleniti ipsum atque modi libero consequuntur, porro labore autem quod! Voluptatibus eos eveniet optio nemo atque.</p > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const { createApp, onMounted, ref } = Vue ;const app = createApp ({ setup ( ) { const p = ref (null ); onMounted (() => { console .log ('DOM:' , p.value ); }) return { p, } } }); app.mount ('#app' );
兩者寫法攤開來看就可以看出兩者的差異,那麼接著拉回到 React 的部分。
相信你看到上面的範例之後,大概就很清楚 useRef 在實戰上還可以用於選取 DOM 了吧?所以就來示範一下吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const App = ( ) => { const pRef = React .useRef (null ); React .useEffect (() => { console .log ('DOM:' , pRef.current ); }); return ( <div > <p ref ={ pRef }> Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati ad assumenda laborum id alias? Reiciendis fugiat, explicabo, natus aperiam debitis facilis quia consequatur voluptatum veniam nobis nulla ad perspiciatis in. </p > </div > ) }const root = ReactDOM .createRoot (document .querySelector ('#root' )); root.render (<App /> );
除此之外 useRef 也可以用來選取元件,但是這邊我就不額外示範了,而是保留給你自己嘗試看看囉。
後記 本文將會同步更新到我的部落格
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement