React高级话题之Refs and the DOM
前言
本文為意譯,翻譯過程中摻雜本人的理解,如有誤導(dǎo),請(qǐng)放棄繼續(xù)閱讀。
原文地址:Refs and the DOM
正文
Refs提供了一種訪問在render方法里面創(chuàng)建的React element或者原生DOM節(jié)點(diǎn)的方法。
在典型的React數(shù)據(jù)流中(自上而下的數(shù)據(jù)流),props是父組件與子組件打交道的唯一途徑。為了與子組件交互,你需要給子組件傳遞一個(gè)新的props,促使它重新渲染。然而,有不少的場(chǎng)景需要我們?cè)谶@種props主導(dǎo)型的數(shù)據(jù)流之外去命令式地去修改子組件里面的東西。被修改的子組件有可能是一個(gè)React component的實(shí)例,也有可能是一個(gè)原生DOM元素。對(duì)于這兩種情況,React都提供了一個(gè)“安全艙口”去訪問它們。
什么時(shí)候用Refs呢?
以下幾個(gè)業(yè)務(wù)場(chǎng)景是挺合適的:
- 手動(dòng)管理聚焦(focus),文本選擇或者視頻,音頻的回放。
- 命令式地觸發(fā)動(dòng)畫。
- 與第三方的DOM類庫進(jìn)行整合。
如果能用聲明式的方式去實(shí)現(xiàn)的,就不要用refs去實(shí)現(xiàn)。舉個(gè)例子說,能通過傳遞一個(gè)isOpenprop給Dialog組件來實(shí)現(xiàn)彈窗的打開和關(guān)閉,就不用通過暴露“open”和“close”方法來實(shí)現(xiàn)彈窗的打開和關(guān)閉(在結(jié)合redux數(shù)據(jù)流的背景下,我不太認(rèn)同這句話所表達(dá)的觀點(diǎn))。
不要濫用Refs
在實(shí)際開發(fā)的過程中,如果實(shí)現(xiàn)上遇到困難了,你的慣性思維可能是,先使用refs實(shí)現(xiàn)了它(這個(gè)功能),管它三七二十一呢。在這種情況下,你不妨先讓你的頭腦冷靜下來,以更縝密的思維去想想,能不能通過state來實(shí)現(xiàn)呢?如果能,state應(yīng)該存放在組件樹層級(jí)中的哪個(gè)層級(jí)呢?一般來說,公認(rèn)為合適存放state的層級(jí)是頂級(jí)組件,也就是我們以前說的“container component”。查看提升你的state 看看該怎么做。
注意,接下來的例子已經(jīng)被更新過了。更新過后的例子使用了React.createRef()這個(gè)API。這個(gè)API在React 16.3中就引入了。假如你還在使用較早的React版本,那么我們推薦你使用callback refs來代替。
Object Refs
創(chuàng)建Refs
使用React.createRef()來創(chuàng)建refs,并通過ref屬性來attached to React element。一般的做法是,在組件的constructor里面,將通過React.createRef()來創(chuàng)建的refs直接賦值給組件的一個(gè)實(shí)例屬性。這樣一來,當(dāng)組件實(shí)例化的時(shí)候,你就可以在組件的其他地方使用這個(gè)引用。(也就是說,組件實(shí)例的某個(gè)屬性是引用著通過React.createRef()來創(chuàng)建的Refs的,而這個(gè)refs又是通過ref屬性附加在原生DOM元素或者子組件實(shí)例上的。故通過這個(gè)實(shí)例屬性,我們是可以訪問到原生DOM元素或者子組件實(shí)例的)
class MyComponent extends React.Component {constructor(props) {super(props);this.myRef = React.createRef();}render() {return <div ref={this.myRef} />;} }訪問Refs
上面講了如何創(chuàng)建refs,這一小節(jié)我們就來講如何訪問refs。其實(shí)上面已經(jīng)講到了,我們通過組件的實(shí)例屬性來保存著refs的引用的。refs是一個(gè)對(duì)象,它有一個(gè)current屬性。我們正是通過這個(gè)current屬性來訪問原生DOM元素或者子組件實(shí)例的。
const node = this.myRef.current;current的屬性值因不同的情況而異。這個(gè)不同的情況指的是ref屬性所在的React component的類型。
- DOM component。當(dāng)ref屬性是負(fù)載在DOM component上的話,通過React.createRef()來創(chuàng)建的Refs對(duì)象的current屬性的值將會(huì)是原生的DOM元素。
- custom component。custom component又可以分為class component 和function component。注意,因?yàn)閒unction component是沒有實(shí)例的,所以,它是不能通過這種方式來使用ref屬性的(這里,這種方式是指上面“創(chuàng)建refs”這一小節(jié)所說的方法。實(shí)際上function component也是可以消費(fèi)ref屬性的,這得使用后面提到的“ ref forwarding”技術(shù))。所以這里,custom component指的是class component。當(dāng)ref屬性是掛載在custom component上的話,通過React.createRef()來創(chuàng)建的refs對(duì)象的current屬性將會(huì)指向custom component的組件實(shí)例。
下面的例子將會(huì)演示這兩者之間的不同。
1)把ref屬性掛載在DOM component上
下面的代碼使用了ref屬性來保存DOM元素的引用:
class CustomTextInput extends React.Component {constructor(props) {super(props);// create a ref to store the textInput DOM elementthis.textInput = React.createRef();this.focusTextInput = this.focusTextInput.bind(this);}focusTextInput() {// Explicitly focus the text input using the raw DOM API// Note: we're accessing "current" to get the DOM nodethis.textInput.current.focus();}render() {// tell React that we want to associate the <input> ref// with the `textInput` that we created in the constructorreturn (<div><inputtype="text"ref={this.textInput} /><inputtype="button"value="Focus the text input"onClick={this.focusTextInput}/></div>);}當(dāng)ref屬性所在的那個(gè)組件掛載到頁面后,current屬性將會(huì)被賦值為指向原生DOM元素的引用。當(dāng)這個(gè)組件被卸載后,current屬性又會(huì)被重置為null。ref屬性值的更新發(fā)生在組件生命周期函數(shù)componentDidMount和componentDidUpdate之前。
2)把ref屬性掛載在class component上
如果你想把上面的<CustomTextInput>包裹在父組件中,并想模擬組件掛載后就自動(dòng)獲取焦點(diǎn)。那么,我們可以通過ref去訪問那個(gè)<CustomTextInput>的實(shí)例,通過這個(gè)實(shí)例的focusTextInput方法手動(dòng)地讓對(duì)應(yīng)的組件內(nèi)部的input框獲得焦點(diǎn)。
class AutoFocusTextInput extends React.Component {constructor(props) {super(props);this.textInput = React.createRef();}componentDidMount() {// 在這里this.textInput.current指向的是// CustomTextInput組件的實(shí)例this.textInput.current.focusTextInput();}render() {return (<CustomTextInput ref={this.textInput} />);} }注意,<CustomTextInput>組件是class component時(shí),這種寫法才會(huì)有用。
class CustomTextInput extends React.Component {// ... }3)Refs與function component的關(guān)聯(lián)
注意,第三點(diǎn),我們已經(jīng)不使用“把ref屬性掛載在xxx組件上”這個(gè)說法了。因?yàn)榘裷ef屬性直接掛載function component是沒有什么用的。這里用“關(guān)聯(lián)”一詞,只不過在表達(dá),refs還是可以跟function component結(jié)合起來使用的。 再次強(qiáng)調(diào),應(yīng)為function component沒有實(shí)例,所以不要直接在function component上掛載ref屬性:
function MyFunctionComponent() {return <input />; }class Parent extends React.Component {constructor(props) {super(props);this.textInput = React.createRef();}render() {// This will *not* work!return (<MyFunctionComponent ref={this.textInput} />);} }如果你想在一個(gè)組件上直接掛載ref屬性,那么你需要將這個(gè)組件轉(zhuǎn)化為class component。這種轉(zhuǎn)化,就像你如果需要使用生命周期函數(shù)或者state,你也會(huì)將組件轉(zhuǎn)化為class component一樣。
然而,正如我們上面提到的,ref屬性還是可以跟function component結(jié)合使用的。如何結(jié)合法呢?那就是在function component的實(shí)現(xiàn)代碼體里面使用。值得注意的是,即使在function component里面去使用,ref屬還是要掛載在DOM component或者class component上:
function CustomTextInput(props) {// textInput must be declared here so the ref can refer to itlet textInput = React.createRef();function handleClick() {textInput.current.focus();}return (<div><inputtype="text"ref={textInput} /><inputtype="button"value="Focus the text input"onClick={handleClick}/></div>); }將DOM Refs暴露給父組件
在很少的情況下,你可能想直接從父組件來訪問子組件里面的DOM元素。而一般情況下,我們是不推薦大家這么做的。因?yàn)檫@么做會(huì)打破組件封裝的完整性。但是呢,偶爾這么做還是挺管用的。比如想手動(dòng)讓輸入框獲取焦點(diǎn)或者測(cè)量子組件中DOM元素的位置和尺寸大小。
如果你正在使用React 16.3或者以上,我們推薦你使用ref forwarding來滿足你的需要。Ref forwarding技術(shù)讓子組件自己選擇是否要把ref引用暴露給父組件(Ref forwarding lets components opt into exposing any child component’s ref as their own)。你可以查閱一下ref forwarding文檔。這里的例子將會(huì)給你演示如何地將子組件中DOM元素的ref引用暴露給父組件的。
如果你再用React 16.2或者以下,或者需要一個(gè)比ref fowarding更加靈活的方案,你可以使用這個(gè)方案。通過一個(gè)不同與ref的prop名,顯式地將ref引用傳遞下去。
我們建議盡可能少地去暴露DOM元素給外界。但是,它確實(shí)可以是一個(gè)很有用的(訪問原生DOM)“安全艙口”。注意,這種訪問原生DOM的方案需要你往子組件中添加一些代碼。假如你對(duì)子組件的實(shí)現(xiàn)沒有控制權(quán)(即往里面插入一些代碼),那么這個(gè)時(shí)候你只剩下最后的選擇了-使用findDOMNode()。原則上,findDOMNode()已經(jīng)不被鼓勵(lì)使用了。在 StrictMode下,這個(gè)API已經(jīng)被廢棄了。
Callback Refs
除了上面提到的方法外,React也支持別的方式去設(shè)置refs,其中一個(gè)就叫“callback refs”。“callback refs”能夠在refs賦新值和重置的時(shí)候給你更小粒度的控制權(quán)。
相比于使用createRef()來創(chuàng)建refs并將它傳遞給ref屬性,“callback refs”傳遞給ref屬性的是一個(gè)函數(shù),準(zhǔn)備來說是一個(gè)callback函數(shù)。在這個(gè)callback函數(shù)里面,你可以通過參數(shù)獲得一個(gè)訪問React component實(shí)例或者原生的DOM元素的引用。一般的做法,使用一個(gè)組件的實(shí)例屬性來保存這個(gè)引用,方便到處使用:
class CustomTextInput extends React.Component {constructor(props) {super(props);this.textInput = null;this.setTextInputRef = element => {this.textInput = element;};this.focusTextInput = () => {// Focus the text input using the raw DOM APIif (this.textInput) this.textInput.focus();};}componentDidMount() {// autofocus the input on mountthis.focusTextInput();}render() {// Use the `ref` callback to store a reference to the text input DOM// element in an instance field (for example, this.textInput).return (<div><inputtype="text"ref={this.setTextInputRef}/><inputtype="button"value="Focus the text input"onClick={this.focusTextInput}/></div>);} }當(dāng)組件掛載到頁面后,React將會(huì)給這個(gè)callback函數(shù)傳入個(gè)一個(gè)組件實(shí)例或者原生DOM的引用;當(dāng)組件卸載后,React再次給callback函數(shù)傳入null(這里說的“給callback函數(shù)傳入”代表著一次callback的調(diào)用)。React保證refs的更新會(huì)在componentDidMount和componentDidUpdate調(diào)用之前發(fā)生。
正如由React.createRef()創(chuàng)建出來的對(duì)象類型refs一樣,你可以將函數(shù)類型的refs一路傳遞下去。
function CustomTextInput(props) {return (<div><input ref={props.inputRef} /></div>); }class Parent extends React.Component {render() {return (<CustomTextInputinputRef={el => this.inputElement = el}/>);} }在上面的例子中,父組件<Parent>將函數(shù)類型的refs以一個(gè)叫inputRef的prop傳遞給<CustomTextInput>組件。然后<CustomTextInput>組件用真正的ref屬性傳遞給<input>組件。從結(jié)果來看,<Parent>的實(shí)例屬性inputElement引用的正是我們想要訪問的,<CustomTextInput>組件里面的<input> DOM元素。
Legacy API: String Refs
如果你之前有使用過React,那么你應(yīng)該知道ref屬性的值可以是一個(gè)字符串,比如:“textInput”。然后你可以通過this.refs.textInput來訪問原生的DOM元素。我們強(qiáng)烈建議你不要再使用它了。因?yàn)樗嬖谀承﹩栴},同時(shí)它已經(jīng)被遺棄了。在未來的某個(gè)版本中,很有可能會(huì)把它的實(shí)現(xiàn)從代碼中移除掉。
注意,如果你現(xiàn)在正在使用this.refs.textInput來訪問refs,那么我們推薦你使用callback pattern或者createRef API來代替。
使用callback refs的注意點(diǎn)
如果你把一個(gè)inline function直接賦值給ref屬性的話,那么這個(gè)inline function將會(huì)被調(diào)用兩次。第一次是以null來調(diào)用的。第二次才是以真實(shí)的DOM元素去調(diào)用。這是因?yàn)?#xff0c;每一次render方法被調(diào)用的時(shí)候,inline function都會(huì)創(chuàng)建一個(gè)新的函數(shù)實(shí)例給ref屬性。所以,React需要先移除舊的ref callback,再來設(shè)置新的。為了避免這個(gè)問題,你可以通過把這個(gè)inline function綁定成class component的方法,使之成為引用,然后將這個(gè)引用賦值給ref屬性。不過大多數(shù)情況下,inline function不會(huì)造成什么大問題的。
零基礎(chǔ)入門
對(duì)于從來沒有接觸過網(wǎng)絡(luò)安全的同學(xué),我們幫你準(zhǔn)備了詳細(xì)的學(xué)習(xí)成長路線圖。可以說是最科學(xué)最系統(tǒng)的學(xué)習(xí)路線,大家跟著這個(gè)大的方向?qū)W習(xí)準(zhǔn)沒問題。
同時(shí)每個(gè)成長路線對(duì)應(yīng)的板塊都有配套的視頻提供:
因篇幅有限,僅展示部分資料,需要點(diǎn)擊下方鏈接即可前往獲取
CSDN大禮包:《黑客&網(wǎng)絡(luò)安全入門&進(jìn)階學(xué)習(xí)資源包》免費(fèi)分享
視頻配套資料&國內(nèi)外網(wǎng)安書籍、文檔&工具
當(dāng)然除了有配套的視頻,同時(shí)也為大家整理了各種文檔和書籍資料&工具,并且已經(jīng)幫大家分好類了。
因篇幅有限,僅展示部分資料,需要點(diǎn)擊下方鏈接即可前往獲取
CSDN大禮包:《黑客&網(wǎng)絡(luò)安全入門&進(jìn)階學(xué)習(xí)資源包》免費(fèi)分享
總結(jié)
以上是生活随笔為你收集整理的React高级话题之Refs and the DOM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# PDF附件生成
- 下一篇: 毛算计算开方运算