useRef
儲存的值不會因為元件重新渲染而改變,適合用來保存不需要觸發重新渲染的資料,像是表單輸入框的狀態、倒數計時器等。useRef
最常見的用途是用來存取 DOM 元素的引用,方便操作該元素。useRef
也常被用來將 DOM 元素的引用傳遞給第三方套件。除了一般常見的 useRef 定義方式,React 還提供了另一種方式叫 callback ref。callback ref 是將一個函數傳遞給 ref
屬性,在一些特殊情況很實用。
如果想自動讓某個元素 focus,一般常見的寫法如下:
import { useEffect, useRef } from "react";
export default function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement | null>(null);
// 因為元素首次 render 還沒有 mounted,所以參數的預設值為 null。
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
但如果根據條件渲染元素時,ref
會是 null
,所以即使執行 useEffect
還是不會 focus 。
import { useState, useEffect, useRef } from "react";
export default function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement | null>(null);
const [showInput, setShowInput] = useState(false);
useEffect(() => {
inputRef.current?.focus();
}, []);
return (
<div>
<button onClick={() => setShowInput(true)}>Show Input</button>
{showInput && <input ref={inputRef} type="text" />}
</div>
);
}
可以透過 callback ref 解決
import { useState, useCallback } from "react";
export default function AutoFocusInput() {
const [showInput, setShowInput] = useState(false);
const inputRef = useCallback((node: HTMLInputElement | null) => {
node?.focus();
}, []);
return (
<div>
<button onClick={() => setShowInput(true)}>Show Input</button>
{showInput && <input ref={inputRef} type="text" />}
</div>
);
}
透過 useCallback
避免每次渲染都重新建立 callback 函數。
可以參考 React 文件的這個例子。
其他更多使用情境可以參考這篇文章。
在 React 19 有新增了 callback cleanup function,類似 useEffect
,當元件 unmount 時執行。
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when element is removed from DOM.
return () => {
// ref cleanup
};
}}
/>
若要使用 React 19 ,要先更新到最新版本。
npm install --save-exact react@rc react-dom@rc
forwardRef 是一個高階函數 (high-order function),允許將 ref
轉發到函數元件內部的某個子元素。這樣就能夠在函數元件中獲取到內部 DOM 元素的引用。
functional components 預設是不支援直接使用 ref 的。這是因為 functional components 本身並沒有實例(instance),不像 class components 有 this 來指向元件實例。因此如果你直接在 functional components 上使用 ref,會無法取得預期的結果並出現錯誤訊息。
import { ComponentProps, forwardRef } from "react";
type ButtonProps = ComponentProps<"button"> & {
icon?: React.ReactNode;
text: string;
};
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ icon, text, ...props }, ref) => {
return (
<button
ref={ref}
className="flex items-center space-x-2 px-4 py-2 bg-blue-500 text-white rounded-md"
{...props}
>
{icon && <span>{icon}</span>}
<span>{text}</span>
</button>
);
}
);
export default Button;
在 React 19 後就可以不用用到 forwardRef 了,可以直接使用 ref 作為 props。
function CustomInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// ...
<CustomInput ref={ref} />;
參考資料:
https://tkdodo.eu/blog/avoiding-use-effect-with-callback-refs
https://react.dev/learn/manipulating-the-dom-with-refs#
https://julesblom.com/writing/ref-callback-use-cases
https://react.dev/blog/2024/04/25/react-19
https://react.dev/reference/react/forwardRef#
https://www.youtube.com/watch?v=m4QbeS9BTNU