React Hooks (3) useRef

ref 基本使用

當你希望 react 記住一個值,但又不想像 useState 會刷新元件時,可以存在 ref 裡。

1
2
3
4
5
6
7
// 使用 ref 儲存值,可以是字串、數字、物件、陣列等。
const ref = useRef(0);

// ref 的值會存在物件的 .current 屬性中,如下:
{
current: 0 // The value you passed to useRef
}

綁定 dom 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { useEffect, useRef } = React;

let myModal;
const App = () => {
const inputRef = useRef(null);
useEffect(() => {
console.log(inputRef);
});
return (
<>
<button type="button" className="btn btn-primary" ref={inputRef}>
打開 Modal
</button>
</>
);
};
  1. 將 useRef 從 react 解構出來
  2. 用 useRef 宣告變數,可以先帶入 null
  3. 在 dom 上加入 ref,綁定宣告好的變數
  4. useEffect(),就可取得 ref 的 dom 元素

綁定 bootstrap modal 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const App = () => {
// 2. 操作 Bootstrap 改為使用 ref
const myModal = useRef(null); // 1. 先宣告 myModal
const modalRef = useRef(null);
const openModal = () => {
myModal.current.show();
};
useEffect(() => {
myModal.current = new bootstrap.Modal(modalRef.current);
});

return (
<>
<div className="modal fade" tabIndex="-1" ref={modalRef}></div>
</>
);
};

useRef 不會刷新元件

和 useState 不同,使用 useRef 定義值時,不會重新刷新元件。

1
2
3
4
5
6
7
8
9
10
11
const [num, setNum] = useState(1);
const num2 = useRef(1);

useEffect(() => {
setInterval(() => {
// 會刷新元件,所以要用 pre 取得前一筆狀態再累加值
setNum((pre) => pre + 1);
// 不會刷新元件,會正確累加值
num2.current = num2.current + 1;
}, 1000);
}, []);

Ref 錯誤用法

要避免使用 Ref 取得 dom 元素後,直接賦予值到 Ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const App = () => {
const [name, setName] = useState("");
const inputRef = useRef(null);

function focus() {
inputRef.current.focus();
inputRef.current.value = "Some value";
}

return (
<>
<input
ref={inputRef}
value={name}
onChange={(e) => setName(e.target.value)}
/>
<div>My Name is {name}</div>
<button onClick={focus}>Focus</button>
</>
);
};

如上 inputRef 的 value 被手動賦值,但 input 綁定的 name 並沒有改變。React 通常使用狀態(state)來管理表單輸入,這樣可以確保 UI 與數據的一致性。當你直接修改 input 的 value 時,這不會觸發 React 的狀態更新,因此 UI 和狀態之間可能會不同步。

應該使用 React 的 setName 函數來更新 name 狀態,而不是直接修改 DOM 值:

1
2
3
4
function focus() {
inputRef.current.focus();
setName("Some value");
}

動態綁定 ref

假設我們有一個待辦事項列表,每一項需要一個 ref,例如可以讓我們聚焦或操作該項元素。通常情況下,我們不能事先知道有多少項目,因此無法直接使用 useRef 為每個項目分配一個獨立的 ref。這時可以用 ref callback 來動態地為每個列表項分配 ref。

具體實現

以下是實現步驟:

  1. 建立一個 refs 物件:使用 refs 物件來儲存每個項目的 ref。

  2. 使用 ref 回調:透過回調函數為每個項目設定 ref,並將該 ref 存儲在 refs 物件中。這樣可以確保每個項目的 ref 都被記錄在 refs 中,並可根據需求進行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useRef } from 'react';

function TodoList({ items }) {
const refs = useRef({});

function setRef(index) {
return (element) => {
refs.current[index] = element;
};
}

return (
<ul>
{items.map((item, index) => (
<li key={item.id} ref={setRef(index)}>
{item.text}
</li>
))}
</ul>
);
}

代碼解釋

  • refs 是一個使用 useRef 創建的物件,用於存儲所有項目的 ref。
  • setRef 函數是回調函數,會返回一個函數並將元素節點(element)存入 refs 中對應的索引位。這個回調函數在渲染時會依據 index 自動更新每個項目的 ref。

當需要存取某個特定的項目時,直接透過 refs.current[index] 可以取得該項目的 DOM 節點並進行操作。