跳至主要内容

useRef

11/13/2023 發布

useRef 可以保留資料狀態,並且資料內容的變更不會觸發重新渲染。
有些資料我們不希望它像 useState 會觸發重新渲染,
或是重新渲染之後導致一些變數內容又被初始化。

用法

useState 一樣,宣告時可以指定初始值,
但後續存取要用 current 屬性取出:

function App() {
const [value, setValue] = setValue(0);
const valueRef = useRef(0);

useEffect(() => {
valueRef.current = value;
}, [value]);

return (
<div>
<div>
last value:
{ valueRef.current }
</div>

<div>
current value:
{ value }
</div>
</div>);
}

ref 的變更並不會讓元件重新渲染,所以如範例用 useEffect 改變 valueRef 後,
雖然內容的確被更新了,但在畫面上顯示的 valueRef 還是上一個週期的內容。


存取 DOM

用來存取 DOM 時初始值會指定為 null,並在目標元素上加入屬性 ref={boxRef}來綁定。

function App() {
const boxRef = useRef(null);

useEffect(() => {
// 在第一次渲染後會成功綁定到 DOM
console.log(boxRef.current);
}, []);

return (
<div>
<div
ref={boxRef}
class="box"
>
box
</div>
</div>);
}

常見的案例是要捕捉元素的屬性或方法,
例如長寬大小、scorll bar 的高度,或是操作 <video> 的暫停、播放等。

像 scorll bar 有可能在重新渲染後,因為 DOM 被重新創造,
原本停留在某個高度被刷新回到 top: 0 的位置,
導致使用者必須再滾動一次,這時就需要透過 useRef 搭配 useEffect
讓它在重新渲染後仍然能回到正確的位置。


第三方套件

許多套件的功能是用類別(class)來實現的,如果不使用 useRef 存起來,
會造成每次渲染時都產生新的實例(instance),上一個週期的實例物件內容也都被重置了:

class Foo {
time = null;

constructor(time) {
this.time = time;
}

sayHelloCreatedTime() {
console.log(`Hello, this object is created at ${this.time}`);
}
}

function App() {
const [value, setValue] = useState(0);
const fooObj = new Foo(new Date());
const fooObjRef = useRef(new Foo(new Date()));

useEffect(() => {
fooObj.sayHelloCreatedTime();
fooObjRef.current.sayHelloCreatedTime();
}, [value]);

return (/* 元件內容 */);
}

如上面範例,只要狀態改變,元件內部除了 hook,
在重新渲染時其他程式碼都會重新執行一次,
所以 fooObj 每次在 useEffect 中印出來的 time 都不一樣。
當實例有被 useRef 保留下來後,再操作類別的屬性或方法時,
就不會發生狀態被重置的問題。


參考資料