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