一糊识、useRef 是什么?
const myRef = useRef(initialValue);
- useRef 返回一個(gè)可變的 ref 對(duì)象滨嘱,且只有一個(gè)current屬性射沟,初始值為傳入的參數(shù)( initialValue );
- 返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變恨憎;
- 當(dāng)更新 current 值時(shí)并不會(huì) re-render 蕊退,而 useState 更新值時(shí)會(huì)觸發(fā)頁面渲染;
- 更新 useRef 是 side effect (副作用)憔恳,所以一般寫在 useEffect 或 event handler 里;
- useRef 類似于類組件的 this瓤荔。
二、useRef 可以解決什么問題钥组?
1茉贡、使用useRef來獲取上一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
這個(gè)函數(shù)再渲染過程中總是返回上一次的值,因?yàn)?ref.current 變化不會(huì)觸發(fā)組件的重新渲染者铜,所以需要等到下次的渲染才能顯示到頁面上腔丧。
2、使用useRef來保存不需要變化的值
因?yàn)閡seRef的返回值在組件的每次redner之后都是同一個(gè)作烟,所以它可以用來保存一些在組件整個(gè)生命周期都不需要變化的值愉粤。最常見的就是定時(shí)器的清除場(chǎng)景。
(1)以前用全局變量設(shè)置定時(shí)器
const App = () => {
let timer;
useEffect(() => {
timer = setInterval(() => {
console.log('觸發(fā)了');
}, 1000);
},[]);
const clearTimer = () => {
clearInterval(timer);
}
return (
<>
<Button onClick={clearTimer}>停止</Button>
</>)
}
上面的寫法存在一個(gè)問題拿撩,如果這個(gè)App組件里有state變化或者他的父組件重新render等原因?qū)е逻@個(gè)App組件重新render的時(shí)候衣厘,我們會(huì)發(fā)現(xiàn),點(diǎn)擊停止按鈕压恒,定時(shí)器依然會(huì)不斷的在控制臺(tái)打印影暴,定時(shí)器清除事件無效了。
因?yàn)榻M件重新渲染之后探赫,這里的timer以及clearTimer 方法都會(huì)重新創(chuàng)建型宙,timer已經(jīng)不是定時(shí)器的變量了。
(2)使用useRef定義定時(shí)器
const App = () => {
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
console.log('觸發(fā)了');
}, 1000);
},[]);
const clearTimer = () => {
clearInterval(timer.current);
}
return (
<>
<Button onClick={clearTimer}>停止</Button>
</>)
}
(3) 實(shí)現(xiàn)一個(gè)深度依賴對(duì)比的 useDeepEffect
普通的useEffect只是一個(gè)淺比較的方法伦吠,如果我們依賴的state是一個(gè)對(duì)象妆兑,組件重新渲染,這個(gè)state對(duì)象的值沒變毛仪,但是內(nèi)存引用地址變化了搁嗓,一樣會(huì)觸發(fā)useEffect的重新渲染。
const createObj = () => ({
name: 'zouwowo'
});
useEffect(() => {
// 這個(gè)方法會(huì)無限循環(huán)
}, [createObj()]);
使用 useRef 實(shí)現(xiàn)深度依賴比較
import equal from 'fast-deep-equal';
export useDeepEffect = (callback, deps) => {
const emitEffect = useRef(0);
const prevDeps = useRef(deps);
if (!equal(prevDeps.current, deps)) {
// 當(dāng)深比較不相等的時(shí)候箱靴,修改emitEffect.current的值腺逛,觸發(fā)下面的useEffect更新
emitEffect.current++;
}
prevDeps.current = deps;
return useEffect(callback, [emitEffect.current]);
}
(4)小結(jié)
- useRef 是定義在實(shí)例基礎(chǔ)上的,如果代碼中有多個(gè)相同的組件衡怀,每個(gè)組件的 ref 只跟組件本身有關(guān)棍矛,跟其他組件的 ref 沒有關(guān)系。
- 組件前定義的 global 變量狈癞,是屬于全局的茄靠。如果代碼中有多個(gè)相同的組件,那這個(gè) global 變量在全局是同一個(gè)蝶桶,他們會(huì)互相影響慨绳。
- 組件重新渲染之后,全局變量會(huì)被重新創(chuàng)建真竖,ref 則不會(huì)被刷新脐雪。
3、實(shí)現(xiàn)父組件獲取子組件的屬性和方法
import React, {MutableRefObject, useState, useEffect, useRef, useCallback} from 'react'
interface IProps {
//prettier-ignore
label: string,
cRef: MutableRefObject<any>
}
const ChildInput: React.FC<IProps> = (props) => {
const { label, cRef } = props
const [value, setValue] = useState('')
const handleChange = (e: any) => {
const value = e.target.value
setValue(value)
}
const getValue = useCallback(() => {
return value
}, [value])
useEffect(() => {
if (cRef && cRef.current) {
cRef.current.getValue = getValue
}
}, [getValue])
return (
<div>
<span>{label}:</span>
<input type="text" value={value} onChange={handleChange} />
</div>
)
}
const ParentCom: React.FC = (props: any) => {
const childRef: MutableRefObject<any> = useRef({})
const handleFocus = () => {
const node = childRef.current
alert(node.getValue())
}
return (
<div>
<ChildInput label={'名稱'} cRef={childRef} />
<button onClick={handleFocus}>focus</button>
</div>
)
}
export default ParentCom
父組件按鈕點(diǎn)擊時(shí)恢共,通過調(diào)用getValue战秋,獲取到子組件input里的value值。