useRef
當你在React中需要處理DOM元素或需要在組件渲染之間保持持久性數據時,便可以使用useRef。
import { useRef } from 'react';
const refValue = useRef(initialValue)
refValue.current // 訪問ref的值 類似于vue的ref,Vue的ref是.value,其次就是vue的ref是響應式的,而react的ref不是響應式的
通過Ref操作DOM元素
參數
- initialValue:ref 對象的 current 屬性的初始值。可以是任意類型的值。這個參數在首次渲染后被忽略。
返回值
- useRef返回一個對象,對象的current屬性指向傳入的初始值。
{current:xxxx}
注意
- 改變 ref.current 屬性時,React 不會重新渲染組件。React 不知道它何時會發生改變,因為 ref 是一個普通的 JavaScript 對象。
- 除了 初始化 外不要在渲染期間寫入或者讀取 ref.current,否則會使組件行為變得不可預測。
import { useRef } from "react"
function App() {//首先,聲明一個 初始值 為 null 的 ref 對象let div = useRef(null)const heandleClick = () => {//當 React 創建 DOM 節點并將其渲染到屏幕時,React 將會把 DOM 節點設置為 ref 對象的 current 屬性console.log(div.current)}return (<>{/*然后將 ref 對象作為 ref 屬性傳遞給想要操作的 DOM 節點的 JSX*/}<div ref={div}>dom元素</div><button onClick={heandleClick}>獲取dom元素</button></>)
}
export default App
數據存儲
我們實現一個保存count的新值和舊值的例子,但是在過程中我們發現一個問題,就是num的值一直為0,這是為什么呢?
因為等useState
的 SetCount
執行之后,組件會重新rerender,num的值又被初始化為了0,所以num的值一直為0。
import React, { useLayoutEffect, useRef, useState } from 'react';function App() {let num = 0let [count, setCount] = useState(0)const handleClick = () => {setCount(count + 1)num = count;};return (<div><button onClick={handleClick}>增加</button><div>{count}:{num}</div></div>);
}export default App;
如何修改?
我們可以使用useRef來解決這個問題,因為useRef只會在初始化的時候執行一次,當組件reRender的時候,useRef的值不會被重新初始化。
import React, { useLayoutEffect, useRef, useState } from 'react';function App() {let num = useRef(0)let [count, setCount] = useState(0)const handleClick = () => {setCount(count + 1)num.current = count;};return (<div><button onClick={handleClick}>增加</button><div>{count}:{num.current}</div></div>);
}export default App;
實際應用
我們實現一個計時器的例子,在點擊開始計數的時候,計時器會每300ms執行一次,在點擊結束計數的時候,計時器會被清除。
問題
我們發現,點擊end的時候,計時器并沒有被清除,這是為什么呢?
原因
這是因為組件一直在重新ReRender,所以timer的值一直在被重新賦值為null,導致無法清除計時器。
import React, { useLayoutEffect, useRef, useState } from 'react';function App() {console.log('render')let timer: NodeJS.Timeout | null = nulllet [count, setCount] = useState(0)const handleClick = () => {timer = setInterval(() => {setCount(count => count + 1)}, 300)};const handleEnd = () => {console.log(timer);if (timer) {clearInterval(timer)timer = null}};return (<div><button onClick={handleClick}>開始計數</button><button onClick={handleEnd}>結束計數</button><div>{count}</div></div>);
}export default App;
如何修改?
我們可以使用useRef來解決這個問題,因為useRef的值不會因為組件的重新渲染而改變。
import React, { useLayoutEffect, useRef, useState } from 'react';function App() {console.log('render')let timer = useRef<null | NodeJS.Timeout>(null)let [count, setCount] = useState(0)const handleClick = () => {timer.current = setInterval(() => {setCount(count => count + 1)}, 300)};const handleEnd = () => {if (timer.current) {clearInterval(timer.current)timer.current = null}};return (<div><button onClick={handleClick}>開始計數</button><button onClick={handleEnd}>結束計數</button><div>{count}</div></div>);
}export default App;
注意事項
-
組件在重新渲染的時候,useRef的值不會被重新初始化。
-
改變 ref.current 屬性時,React 不會重新渲染組件。React 不知道它何時會發生改變,因為 ref 是一個普通的 JavaScript 對象。
-
useRef的值不能作為useEffect等其他hooks的依賴項,因為它并不是一個響應式狀態。
-
useRef不能直接獲取子組件的實例,需要使用forwardRef。