目錄:
- 簡(jiǎn)介
- useRef
- forwardRef
- useImperativeHandle
- 回調(diào)Ref
簡(jiǎn)介
大家都知道React中的ref
屬性可以幫助我們獲取子組件的實(shí)例或者Dom對(duì)象壮莹,進(jìn)而對(duì)子組件進(jìn)行修改稿茉,是一個(gè)很方便的特性弟晚。在傳統(tǒng)類組件中,我們通過使用 React.createRef()
創(chuàng)建的蛹稍,并通過 ref
屬性附加到 React 元素
來使用。而隨著hooks的越來越廣泛的使用部服,我們有必要了解一下在函數(shù)式組件中唆姐,如何使用Ref.
想要在函數(shù)式組件中使用Ref,我們必須先了解兩個(gè)Api,useRef
和forwardRef
useRef
const refContainer = useRef(initialValue);
useRef返回一個(gè)可變的ref對(duì)象,其.current
屬性被初始化為傳入的參數(shù)(initialValue)
廓八。返回的ref對(duì)象在整個(gè)生命周期內(nèi)保持不變奉芦。
下面看一個(gè)例子
function TextInputWithFocusButton() {
// 關(guān)鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關(guān)鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關(guān)鍵代碼
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
效果如下
可以看到我們點(diǎn)擊button,先通過useRef
創(chuàng)建一個(gè)ref對(duì)象inputEl
剧蹂,然后再將inputEl
賦值給input
的ref
,最后声功,通過inputEl.current.focus()
就可以讓input聚焦。
然后国夜,我們?cè)傧胂录踉耄绻鹖nput不是個(gè)普通的dom元素,而是個(gè)組件车吹,該怎么辦呢?
這就牽扯到另外一個(gè)api,forwardRef
醋闭。
forwardRef
我們修改一下上面的例子窄驹,將input單獨(dú)封裝成一個(gè)組件TextInput
。
const TextInput = forwardRef((props,ref) => {
return <input ref={ref}></input>
})
然后用TextInputWithFocusButton
調(diào)用它
function TextInputWithFocusButton() {
// 關(guān)鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關(guān)鍵代碼证逻,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關(guān)鍵代碼
<TextInput ref={inputEl}></TextInput>
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
可以看到React.forwardRef 接受一個(gè)渲染函數(shù)乐埠,其接收 props 和 ref 參數(shù)并返回一個(gè) React 節(jié)點(diǎn)。
這樣我們就將父組件中創(chuàng)建的ref
轉(zhuǎn)發(fā)進(jìn)子組件囚企,并賦值給子組件的input元素丈咐,進(jìn)而可以調(diào)用它的focus方法。
至此為止龙宏,通過useRef+forwardRef棵逊,我們就可以在函數(shù)式組件中使用ref了。當(dāng)然银酗,這篇文章還遠(yuǎn)不止如此辆影,下面還要介紹兩個(gè)重要的知識(shí)點(diǎn)useImperativeHandle
和回調(diào)Ref
,結(jié)合上面兩個(gè)api,讓你的代碼更加完美黍特。
useImperativeHandle
有時(shí)候蛙讥,我們可能不想將整個(gè)子組件暴露給父組件,而只是暴露出父組件需要的值或者方法灭衷,這樣可以讓代碼更加明確次慢。而useImperativeHandle
Api就是幫助我們做這件事的。
語法:
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以讓你在使用 ref 時(shí)自定義暴露給父組件的實(shí)例值。
一個(gè)例子??:
const TextInput = forwardRef((props,ref) => {
const inputRef = useRef();
// 關(guān)鍵代碼
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />
})
function TextInputWithFocusButton() {
// 關(guān)鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關(guān)鍵代碼迫像,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關(guān)鍵代碼
<TextInput ref={inputEl}></TextInput>
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
這樣拭抬,我們也可以使用current.focus()來事input聚焦。這里要注意的是侵蒙,子組件TextInput中的useRef對(duì)象造虎,只是用來獲取input元素的,大家不要和父組件的useRef混淆了纷闺。
回調(diào)Ref
什么是回調(diào)Ref
呢算凿?
上面的例子,都有一個(gè)問題犁功,就是當(dāng) ref
對(duì)象內(nèi)容發(fā)生變化時(shí)氓轰,useRef
并不會(huì)通知你。變更 .current
屬性不會(huì)引發(fā)組件重新渲染浸卦,看下面這個(gè)例子署鸡。
比如下面這個(gè)例子:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const [value, setValue] = useState("");
useEffect(() => {
setValue(inputEl.current.value);
}, [inputEl]);
const onButtonClick = () => {
// `current` 指向已掛載到 DOM 上的文本輸入元素
console.log("input值", inputEl.current.value);
setValue(inputEl.current.value);
};
return (
<>
<div>
子組件: <TextInput ref={inputEl}></TextInput>
</div>
<div>
父組件: <input type="text" value={value} onChange={() => {}} />
</div>
<button onClick={onButtonClick}>獲得值</button>
</>
);
}
const TextInput = forwardRef((props, ref) => {
const [value, setValue] = useState("");
const inputRef = useRef();
useImperativeHandle(ref, () => ({
value: inputRef.current.value,
}));
const changeValue = e => {
setValue(e.target.value);
};
return <input ref={inputRef} value={value} onChange={changeValue}></input>;
});
可以看到,父組件獲取不到子組件實(shí)時(shí)的值限嫌,必須點(diǎn)擊按鈕才能獲取到靴庆,即使我寫了useEffect
,希望它在inputEl
改變的時(shí)候怒医,重新設(shè)置value
的值炉抒。
那怎么辦呢?這就需要回調(diào)Ref稚叹,我們改一下代碼
function TextInputWithFocusButton() {
const [value, setValue] = useState("");
const inputEl = useCallback(node => {
if (node !== null) {
console.log("TCL: TextInputWithFocusButton -> node.value", node.value)
setValue(node.value);
}
}, []);
const onButtonClick = () => {
// `current` 指向已掛載到 DOM 上的文本輸入元素
console.log("input值", inputEl.current.value);
setValue(inputEl.current.value);
};
return (
<>
<div>
子組件: <TextInput ref={inputEl}></TextInput>
</div>
<div>
父組件: <input type="text" value={value} onChange={() => {}} />
</div>
</>
);
}
const TextInput = forwardRef((props,ref) => {
const [value, setValue] = useState('')
const inputRef = useRef();
useImperativeHandle(ref, () => ({
value: inputRef.current.value
}));
const changeValue = (e) =>{
setValue(e.target.value);
}
return <input ref={inputRef} value={value} onChange={changeValue}></input>
})
可以看到焰薄,這里我們輸入時(shí),父組件就可以實(shí)時(shí)地拿到值了扒袖。
這里比較關(guān)鍵的代碼就是使用useCallback
代替了useRef
塞茅,callback ref
會(huì)將當(dāng)前ref的值變化通知我們。
好季率,以上就是整理的關(guān)于函數(shù)式組件中使用Ref的方法野瘦。
當(dāng)然,其中關(guān)于api介紹的比較簡(jiǎn)陋蚀同,大家看完可能不知所云缅刽。因?yàn)榇似恼碌闹饕康模瑑H是將散落在官網(wǎng)中關(guān)于ref的相關(guān)方法進(jìn)行一下整合蠢络,形成一個(gè)使用ref的思路衰猛,關(guān)于api的更深入的了解,還請(qǐng)移步React官網(wǎng)刹孔。
如有不足啡省,歡迎指出娜睛。
參考文獻(xiàn):
官網(wǎng)中useRef useImperativeHandle Api的介紹
官網(wǎng)中forwardRef的介紹
官網(wǎng)中回調(diào)Ref的介紹