目錄:
- 簡介
- useRef
- forwardRef
- useImperativeHandle
- 回調Ref
簡介
大家都知道React中的ref
屬性可以幫助我們獲取子組件的實例或者Dom對象,進而對子組件進行修改,是一個很方便的特性。在傳統類組件中,我們通過使用 React.createRef()
創建的,並通過 ref
屬性附加到 React 元素
來使用。而隨着hooks的越來越廣泛的使用,我們有必要了解一下在函數式組件中,如何使用Ref.
想要在函數式組件中使用Ref,我們必須先了解兩個Api,useRef
和forwardRef
useRef
const refContainer = useRef(initialValue);
useRef返回一個可變的ref對象,其.current
屬性被初始化為傳入的參數(initialValue)
。返回的ref對象在整個生命周期內保持不變。
下面看一個例子
function TextInputWithFocusButton() {
// 關鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關鍵代碼
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
效果如下
可以看到我們點擊button,先通過useRef
創建一個ref對象inputEl
,然后再將inputEl
賦值給input
的ref
,最后,通過inputEl.current.focus()
就可以讓input聚焦。
然后,我們再想下,如果input不是個普通的dom元素,而是個組件,該怎么辦呢?
這就牽扯到另外一個api,forwardRef
。
forwardRef
我們修改一下上面的例子,將input單獨封裝成一個組件TextInput
。
const TextInput = forwardRef((props,ref) => {
return <input ref={ref}></input>
})
然后用TextInputWithFocusButton
調用它
function TextInputWithFocusButton() {
// 關鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關鍵代碼
<TextInput ref={inputEl}></TextInput>
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
可以看到React.forwardRef 接受一個渲染函數,其接收 props 和 ref 參數並返回一個 React 節點。
這樣我們就將父組件中創建的ref
轉發進子組件,並賦值給子組件的input元素,進而可以調用它的focus方法。
至此為止,通過useRef+forwardRef,我們就可以在函數式組件中使用ref了。當然,這篇文章還遠不止如此,下面還要介紹兩個重要的知識點useImperativeHandle
和回調Ref
,結合上面兩個api,讓你的代碼更加完美。
useImperativeHandle
有時候,我們可能不想將整個子組件暴露給父組件,而只是暴露出父組件需要的值或者方法,這樣可以讓代碼更加明確。而useImperativeHandle
Api就是幫助我們做這件事的。
語法:
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以讓你在使用 ref 時自定義暴露給父組件的實例值。
一個例子🌰:
const TextInput = forwardRef((props,ref) => {
const inputRef = useRef();
// 關鍵代碼
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />
})
function TextInputWithFocusButton() {
// 關鍵代碼
const inputEl = useRef(null);
const onButtonClick = () => {
// 關鍵代碼,`current` 指向已掛載到 DOM 上的文本輸入元素
inputEl.current.focus();
};
return (
<>
// 關鍵代碼
<TextInput ref={inputEl}></TextInput>
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
這樣,我們也可以使用current.focus()來事input聚焦。這里要注意的是,子組件TextInput中的useRef對象,只是用來獲取input元素的,大家不要和父組件的useRef混淆了。
回調Ref
什么是回調Ref
呢?
上面的例子,都有一個問題,就是當 ref
對象內容發生變化時,useRef
並不會通知你。變更 .current
屬性不會引發組件重新渲染,看下面這個例子。
比如下面這個例子:
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>;
});
可以看到,父組件獲取不到子組件實時的值,必須點擊按鈕才能獲取到,即使我寫了useEffect
,希望它在inputEl
改變的時候,重新設置value
的值。
那怎么辦呢?這就需要回調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>
})
可以看到,這里我們輸入時,父組件就可以實時地拿到值了。
這里比較關鍵的代碼就是使用useCallback
代替了useRef
,callback ref
會將當前ref的值變化通知我們。
好,以上就是整理的關於函數式組件中使用Ref的方法。
當然,其中關於api介紹的比較簡陋,大家看完可能不知所雲。因為此篇文章的主要目的,僅是將散落在官網中關於ref的相關方法進行一下整合,形成一個使用ref的思路,關於api的更深入的了解,還請移步React官網。
如有不足,歡迎指出。
參考文獻:
官網中useRef useImperativeHandle Api的介紹
官網中forwardRef的介紹
官網中回調Ref的介紹