React新文檔切記不要濫用Ref
引言
React新文檔有個很有意思的細(xì)節(jié):useRef、useEffect這兩個API的介紹,在文檔中所在的章節(jié)叫Escape Hatches(逃生艙)。
顯然,正常航行時是不需要逃生艙的,只有在遇到危險時會用到。
如果開發(fā)者過多依賴這兩個API,可能是誤用。
在React新文檔:不要濫用effect哦中我們談到useEffect的正確使用場景。
今天,我們來聊聊Ref的使用場景。
為什么是逃生艙?
先思考一個問題:為什么ref、effect被歸類到逃生艙中?
這是因為二者操作的都是脫離React控制的因素。
effect中處理的是副作用。比如:在useEffect中修改了document.title。
document.title不屬于React中的狀態(tài),React無法感知他的變化,所以被歸類到effect中。
同樣,使DOM聚焦需要調(diào)用element.focus(),直接執(zhí)行DOM API也是不受React控制的。
雖然他們是脫離React控制的因素,但為了保證應(yīng)用的健壯,React也要盡可能防止他們失控。
失控的Ref
對于Ref,什么叫失控呢?
首先來看不失控的情況:
- 執(zhí)行
ref.current的focus、blur等方法 - 執(zhí)行
ref.current.scrollIntoView使element滾動到視野內(nèi) - 執(zhí)行
ref.current.getBoundingClientRect測量DOM尺寸
這些情況下,雖然我們操作了DOM,但涉及的都是React控制范圍外的因素,所以不算失控。
但是下面的情況:
- 執(zhí)行
ref.current.remove移除DOM - 執(zhí)行
ref.current.appendChild插入子節(jié)點
同樣是操作DOM,但這些屬于React控制范圍內(nèi)的因素,通過ref執(zhí)行這些操作就屬于失控的情況。
舉個例子,下面是React文檔中的例子:
按鈕1點擊后會插入/移除 P節(jié)點,按鈕2點擊后會調(diào)用DOM API移除P節(jié)點:
export default function Counter() {
const [show, setShow] = useState(true);
const ref = useRef(null);
return (
<div>
<button
onClick={() => {
setShow(!show);
}}>
Toggle with setState
</button>
<button
onClick={() => {
ref.current.remove();
}}>
Remove from the DOM
</button>
{show && <p ref={ref}>Hello world</p>}
</div>
);
}
按鈕1通過React控制的方式移除P節(jié)點。
按鈕2直接操作DOM移除P節(jié)點。
如果這兩種移除P節(jié)點的方式混用,那么先點擊按鈕1再點擊按鈕2就會報錯:

這就是使用Ref操作DOM造成的失控情況導(dǎo)致的。
如何限制失控
現(xiàn)在問題來了,既然叫失控了,那就是React沒法控制的(React總不能限制開發(fā)者不能使用DOM API吧?),那如何限制失控呢?
在React中,組件可以分為:
- 高階組件
- 低階組件
低階組件指那些基于DOM封裝的組件,比如下面的組件,直接基于input節(jié)點封裝:
function MyInput(props) {
return <input {...props} />;
}
在低階組件中,是可以直接將ref指向DOM的,比如:
function MyInput(props) {
const ref = useRef(null);
return <input ref={ref} {...props} />;
}
高階組件指那些基于低階組件封裝的組件,比如下面的Form組件,基于Input組件封裝:
function Form() {
return (
<>
<MyInput/>
</>
)
}
高階組件無法直接將ref指向DOM,這一限制就將ref失控的范圍控制在單個組件內(nèi),不會出現(xiàn)跨越組件的ref失控。
以文檔中的示例為例,如果我們想在Form組件中點擊按鈕,操作input聚焦:
function MyInput(props) {
return <input {...props} />;
}
function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
input聚焦
</button>
</>
);
}
點擊后,會報錯:

這是因為在Form組件中向MyInput傳遞ref失敗了,inputRef.current并沒有指向input節(jié)點。
究其原因,就是上面說的為了將ref失控的范圍控制在單個組件內(nèi),React默認(rèn)情況下不支持跨組件傳遞ref。
人為取消限制
如果一定要取消這個限制,可以使用forwardRef API顯式傳遞ref:
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
使用forwardRef(forward在這里是傳遞的意思)后,就能跨組件傳遞ref。
在例子中,我們將inputRef從Form跨組件傳遞到MyInput中,并與input產(chǎn)生關(guān)聯(lián)。
在實踐中,一些同學(xué)可能覺得forwardRef這一API有些多此一舉。
但從ref失控的角度看,forwardRef的意圖就很明顯了:既然開發(fā)者手動調(diào)用forwardRef破除防止ref失控的限制,那他應(yīng)該知道自己在做什么,也應(yīng)該自己承擔(dān)相應(yīng)的風(fēng)險。
同時,有了forwardRef的存在,發(fā)生ref相關(guān)錯誤后也更容易定位錯誤。
useImperativeHandle
除了限制跨組件傳遞ref外,還有一種防止ref失控的措施,那就是useImperativeHandle,他的邏輯是這樣的:
既然ref失控是由于使用了不該被使用的DOM方法(比如appendChild),那我可以限制ref中只存在可以被使用的方法。
用useImperativeHandle修改我們的MyInput組件:
const MyInput = forwardRef((props, ref) => {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
realInputRef.current.focus();
},
}));
return <input {...props} ref={realInputRef} />;
});
現(xiàn)在,Form組件中通過inputRef.current只能取到如下數(shù)據(jù)結(jié)構(gòu):
{
focus() {
realInputRef.current.focus();
},
}
就杜絕了開發(fā)者通過ref取到DOM后,執(zhí)行不該被使用的API,出現(xiàn)ref失控的情況。
總結(jié)
正常情況,Ref的使用比較少,他是作為逃生艙而存在的。
為了防止錯用/濫用導(dǎo)致ref失控,React限制默認(rèn)情況下,不能跨組件傳遞ref。
為了破除這種限制,可以使用forwardRef。
為了減少ref對DOM的濫用,可以使用useImperativeHandle限制ref傳遞的數(shù)據(jù)結(jié)構(gòu)。
以上就是React新文檔切記不要濫用Ref的詳細(xì)內(nèi)容,更多關(guān)于React新文檔Ref的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用webpack配置react-hot-loader熱加載局部更新
這篇文章主要介紹了使用webpack配置react-hot-loader熱加載局部更新,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
React+TypeScript+webpack4多入口配置詳解
這篇文章主要介紹了React+TypeScript+webpack4多入口配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08

