Nếu ta muốn component lưu một dữ liệu nào đó mà không muốn dữ liệu đó trigger quá trình re-render, ta có thể dùng ref (viết tắt của reference). Giá trị lưu trong ref sẽ được duy trì giữa những lần render.
Sử dụng ref khi ta cần làm việc với các hệ thống bên ngoài, chẳng hạn:
- Lưu id của timeout hoặc interval.
- Lưu và thao tác với các DOM element.
- Lưu các đối tượng mà không cần thiết cho việc tính toán JSX trả về.
Adding a Ref to Your Component
Để sử dụng ref, ta cần dùng hook useRef và truyền vào một giá trị mặc định như sau:
const ref = useRef(0)Ref có thể chứa bất kỳ loại dữ liệu nào và ta thường dùng ref để trỏ đến các DOM element (xem thêm Manipulating the DOM with Refs).
Ref trả về từ đoạn code trên có dạng như sau:
{
current: 0 // The value you passed to useRef
}Ta có thể chỉnh sửa thuộc tính current của ref (ref có tính mutable) mà không làm component bị re-render:
import { useRef } from "react"
function Counter() {
let ref = useRef(0)
function handleClick() {
ref.current = ref.current + 1
alert("You clicked " + ref.current + " times!")
}
return <button onClick={handleClick}>Click me!</button>
}Trong component trên, khi bấm vào nút “Click me” thì giá trị của current sẽ tăng lên 1 và component sẽ không được re-render.
State không thể bị thay đổi ở trong lần render hiện tại (xem thêm State as a Snapshot). Tuy nhiên, khi ta thay đổi ref thì nó sẽ được cập nhật ngay lập tức:
ref.current = 5
console.log(ref.current) // 5Stopwatch Example
Giả sử ta muốn xây dựng một stopwatch cho biết thời gian đã trôi qua.
Để tính thời gian trôi qua, ta cần lưu lại thời gian lúc bắt đầu và thời gian hiện tại. Đây là hai thông tin sẽ thay đổi theo thời gian nên ta cần lưu chúng trong state:
const [startTime, setStartTime] = useState(null)
const [now, setNow] = useState(null)Component sẽ có dạng như sau:
import { useState } from "react"
function Stopwatch() {
const [startTime, setStartTime] = useState(null)
const [now, setNow] = useState(null)
function handleStart() {
// Start counting.
setStartTime(Date.now())
setNow(Date.now())
setInterval(() => {
// Update the current time every 10ms.
setNow(Date.now())
}, 10)
}
let secondsPassed = 0
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
</>
)
}Khi người dùng bấm vào nút “Start”, hai state startTime và now sẽ được thiết lập giá trị là thời điểm bắt đầu. Sau đó, cứ khoảng 10ms thì state now sẽ được cập nhật thành giá trị thời điểm hiện tại. Ở mỗi lần re-render khi now bị thay đổi, chúng ta sẽ tính toán lại thời gian đã trôi qua bằng công thức:
secondsPassed = (now - startTime) / 1000Khi người dùng bấm vào nút “Stop”, ta cần hủy bỏ interval hiện có bằng cách dùng hàm clearInterval. Tuy nhiên, ta cần truyền vào hàm này id của interval. Giá trị id này có thể được lưu ở trong ref.
import { useState, useRef } from "react"
function Stopwatch() {
const [startTime, setStartTime] = useState(null)
const [now, setNow] = useState(null)
const intervalRef = useRef(null)
function handleStart() {
setStartTime(Date.now())
setNow(Date.now())
clearInterval(intervalRef.current)
intervalRef.current = setInterval(() => {
setNow(Date.now())
}, 10)
}
function handleStop() {
clearInterval(intervalRef.current)
}
let secondsPassed = 0
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
)
}Summary
Khi dữ liệu cần dùng cho quá trình render thì ta lưu nó vào state. Còn nếu dữ liệu chỉ được dùng bởi các event handler và thay đổi nó không cần re-render thì ta lưu nó vào ref.