今天要介紹的是可以用來操作 DOM 元素的 useRef 及和它有關的 hook useImperativeHandle。
ref 是一個包含 current 屬性的 JS 物件,它具有以下功用和特性:
例如可以用來避免元件第一次 render 時,useEffect 內的程式碼執行:
react-hooks: skip first run in useEffect
import { useState, useRef, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
const isFirstRun = useRef(true);
useEffect(() => {
// 第一次執行 useEffect 就直接 return 掉,不執行後面的程式碼
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
console.log("Effect was run");
});
return (
<div>
<p>Clicked {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
不要在元件渲染期間去讀和寫 ref 的值。避免的原因是為了保持元件 pure,當前版本的 React 也許還不會出現問題,但未來 React 版本升級就有可能會有影響。
function MyComponent() {
// ...
// 避免改寫 ref 的值
myRef.current = 123;
// ...
// 避免讀取 ref 的值
return <h1>{myOtherRef.current}</h1>;
}
useRef 只會回傳一個值,這個值是一個有 current 屬性的物件,這個 ref.current 可以進行 mutable 的操作。
import React, { useEffect, useRef } from 'react';
const App = () => {
const h1Ref = useRef(); // 定義一個 ref
useEffect(() => {
console.log(h1Ref.current); // 使用 ref 名字.current 取出 dom 元素
}, [])
// 綁定 ref 到 dom 元素上
return <h1 ref={h1Ref}>Hello World!</h1>
}
export default App;
import { useState, useRef, useEffect } from "react";
export default function App() {
const inputRef = useRef();
const clickHandler = () => {
inputRef.current.focus();
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={clickHandler}>Focus</button>
</>
);
}
參考 https://zh-hant.reactjs.org/docs/refs-and-the-dom.html
如果要取得 ref 取到的 DOM 元素底下的子元素,可以加上 children,如範例所示:
useEffect(() => {
console.log(tableRef.current); // table
console.log(tableRef.current.children[0]); // thead
console.log(tableRef.current.children[1]); // tbody
console.log(tableRef.current.children[0].children); // thead tr
...
}, [])
將 ref 傳遞到子元件,讓子元件可以取到父元件的 DOM。
App.js
import { useRef } from "react";
import Child from "./Child";
const App = () => {
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<Child ref={textInput} />
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
};
export default App;
Child.js
import { forwardRef } from "react";
const Child = forwardRef((props, ref) => {
console.log(ref); // <input type="text"></input>
return <input type="text" ref={ref} />;
});
export default Child;
此 hook 會和 forwardRef 一起使用,可以將子元件內的 ref 內置的方法或是值傳遞到母元件,此 hook 較不常使用。
useImperativeHandle(ref, createHandle, [deps])
第二個參數是一個函式,會把裡面的東西綁定到 ref 上,第三個參數作用同 useEffect 的第二個參數,當值改變時重新執行第二個參數
在範例中,透過 useImperativeHandle 將 verify 和 validate 兩個函式從 TextInput 元件傳到 App 元件