引入:如何調用函數式組件內部的方法
對於 React 中需要強制修改子組件的情況,React 提供了 Refs 這種解決辦法,使得我們可以操作底層 DOM 元素或者自定的 class 組件實例。除此之外,文檔(v17.0.1)對函數式組件另有描述:
不能在函數式組件上使用ref屬性,因為他們沒有實例。
在函數式組件和 Hooks 大面積普及的現在,這個特性沒有完全對標 class 組件,令人疑惑。不過經過一陣探索和請教,發現確實是有對應的解決方案的:
useImperativeHandle
結合 React.forwardRef , useImperativeHandle 文檔 應該就能明白是如何使用的。
簡而言之就是可以在函數式組件上使用 ref,通過useImperativeHandle這個hook可以指定暴露給父組件的值和函數。
案例:
修改子組件Counter中的值, 達到重置count的目的:
export default function App() {
return (
<div>
<button>reset</button>
<Counter />
</div>
);
}
/** -------------------------------------- */
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<div>
<hr />
<span>{count}</span>
<button onClick={increment}>+1</button>
</div>
);
}
對於這個案例,將
count這個state往上提一層到 App 組件中是比較合適的,但是在這里重點討論操作子組件。
使用useImperativeHandle,修改代碼:
export default function App() {
const counterRef = useRef();
function reset() {
counterRef.current?.resetCount();
}
return (
<div style={{ padding: 10 }}>
<button onClick={reset}>reset</button>
<MyCounter ref={counterRef} />
</div>
);
}
/** -------------------------------------- */
function Counter(props, ref) {
const [count, setCount] = useState(0);
useImperativeHandle(ref, () => ({
resetCount: resetCount,
}));
function resetCount() {
setCount(0);
}
function increment() {
setCount(count + 1);
}
return (
<div>
<hr />
<span>{count}</span>
<button onClick={increment}>+1</button>
</div>
);
}
const MyCounter = React.forwardRef(Counter);
重點是useImperativeHandle中定義了resetCount,以及使用React.forward獲取 ref,在App組件中為MyCounter中定義ref屬性,然后就可以在外部父組件中使用通過ref調用子組件的resetCount方法。
到這里,實際上已經達到了和class中ref對等的效果。通過給函數式組件設置 ref 並調用組件的方法是可行的,useImperativeHandle除了添加方法,也可以指定值暴露出去。
函數式組件的Ref是什么
將 ref 設置到 HTML 元素上,獲取的是對應的DOM元素,如span:
設置到 class 組件上,獲取的是 class 組件實例:
設置到函數式組件上的時候,獲取的是一個包含可變值或函數的對象,如上例的 Counter 組件:
React.createRef 和 useRef 都是創建了一個包含current屬性的對象,綁定ref時,對應的屬性和函數都在current對應的對象中。
查看對應的TypeScript類型,React.createRef創建的是React.RefObject類型,是只讀的。
而useRef創建的是React.MutableRefObject,是可讀寫的。可以保存任何可變的值,使用方式類似於class組件的this實例變量。(又是和class組件對標的一個點)
文檔描述 useRef 為可以在其
.current屬性中保存一個可變值的“盒子”。
所以實際上應該是,對函數式組件可設置 ref,且設置的 ref 是一個可變對象,存放組件的變量,也能通過useImperativeHandle訪問函數式組件的方法。 但是並不能像將 ref 設置到 class 組件和 DOM 元素上那樣獲取到對應的實例。
