簡單介紹 useRef hook 的使用方法
useRef 是 react 裡面除了 useState 以外另外一個用來儲存資料的 hook,也可以用來取得 DOM 節點。
const ref = useRef(initialValue)
initialValue
: 初始資料ref
: 一個只有 current 屬性的物件,而 current 的 value 會等於你放入的 initialValue
ref.current = somevalue
可以把 useRef 當做一個全域的變數用儲存資料,但是這個變數被改變時並不會觸發 re-render,而且即使元件 re-render 了也還是可以把資料保留下來。
import { useRef } from "react";
function App() {
const [count, setCount] = useState<number>(0);
const numberRef = useRef<number>(0);
function addCount() {
setCount(count + 1);
}
function addNumber() {
numberRef.current += 1;
console.log("numberRef", numberRef.current);
}
console.log("component render");
return (
<div>
<h1>Count: {count}</h1>
<button onClick={addCount}>add Count</button>
<button onClick={addNumber}>add Number</button>
</div>
);
}
可以看到即使 re-render,numberRef.current
的資料還是被保留下來了,而且我是在修改了 numberRef.current
之後就馬上 console,value 也是馬上就被改變了,不像 setState 的改變是非同步的。
其實 useRef 就是一個單純的 JavaScript 物件。
前面幾篇文章都有提到,當我們使用 react 進行開發時會盡量的減少對 DOM 的操作,讓 react 幫我們處理,但是總有非用不可的時候,這個時候就可以透過 useRef
來幫助我們,useRef 也可以用來取得 DOM 元素。
import { useRef } from "react";
import dog from "./assets/dog.mp4";
function App() {
const videoRef = useRef<HTMLVideoElement>(null);
// 處理影片應該播放 / 停止
function togglePlayVideo() {
if (!videoRef.current) return;
if (videoRef.current.paused) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}
// 快進 5 秒
function handleFastForward() {
if (!videoRef.current) return;
videoRef.current.currentTime += 5;
}
// 倒退 5 秒
function handleRewind() {
if (!videoRef.current) return;
videoRef.current.currentTime -= 5;
}
return (
<div>
<h1>useRef</h1>
<video ref={videoRef} src={dog} width="350" controls />
<br />
<button onClick={handleRewind}>倒退 5 秒</button>
<button onClick={togglePlayVideo}>暫停/播放</button>
<button onClick={handleFastForward}>快轉 5 秒</button>
</div>
);
}
每一個 JSX 的 html 標籤都有一個 ref 屬性,value 就是我們用 useRef hook 取得的 ref 物件。
以上面的例子,可以透過 videoRef.current
來取得 ref 放置的 DOM 元素,這裡使用 <video>
來示範,可以做到播放或是修改播放時間來實現快轉、倒退跟暫停的功能。
另外還有一個情形假設有一個影片列表,但是希望可以各別操作,一開始想到的可能會是這樣。
const videoRefOne = useRef<HTMLVideoElement>(null);
const videoRefTwo = useRef<HTMLVideoElement>(null);
一、兩個可能還好,但是如果有十幾個怎麼辦,取得 ref 還有另一個寫法。
ref={(ele) => ele ...}
import { useRef } from "react";
import dog from "./assets/dog.mp4";
const videoList = [
{ videoUrl: dog, id: "1" },
{ videoUrl: dog, id: "2" },
];
function App() {
const videoRefList = useRef<HTMLVideoElement[]>([]);
// 處理影片應該播放 / 停止
function togglePlayVideo(index: number) {
if (!videoRefList.current[index]) return;
if (videoRefList.current[index].paused) {
videoRefList.current[index].play();
} else {
videoRefList.current[index].pause();
}
}
// 快進 5 秒
function handleFastForward(index: number) {
if (!videoRefList.current) return;
videoRefList.current[index].currentTime += 5;
}
// 倒退 5 秒
function handleRewind(index: number) {
if (!videoRefList.current) return;
videoRefList.current[index].currentTime -= 5;
}
return (
<div>
<h1>useRef</h1>
{videoList.map(({ videoUrl, id }, index) => (
<div key={id} style={{ display: "inline-block", margin: "5px" }}>
<video
ref={(ele) => ele && (videoRefList.current[index] = ele)}
src={videoUrl}
width="350"
controls
/>
<br />
<button onClick={() => handleRewind(index)}>倒退 5 秒</button>
<button onClick={() => togglePlayVideo(index)}>暫停/播放</button>
<button onClick={() => handleFastForward(index)}>快轉 5 秒</button>
</div>
))}
</div>
);
}
ref 的屬性可以接收一個 function 作為 value,當使用 function 作為 value 時,那個 function 可以接收到元素本身,可以透過 (videoRefList.current[index] = ele)
這個寫法讓一個 useRef 同時儲存多個元素。
下一篇簡單介紹 react forwardRef 跟 useImperativeHandle
如果內容有誤再麻煩大家指教,我會盡快修改。
這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium