React先進的開發思想一直為社群所稱道,基於資料流的設計極大地簡化了前端開發成本。但如同官方文件所述,web開發中有很多場景需求是脫離資料流的,典型的如處理文字輸入框聚焦(focus)。為此React提供了refs供開發者使用。
筆者是在2015年下半年開始學習React,那時候官方文件關於refs的介紹和使用與現在完全不同。最新的React版本(v15.5.4)已經對這個API進行了修改並更新,不過我們仍可以在文件中找到老版本API的蛛絲馬跡:
我們來比較下新老refs有哪些異同:
在舊版本中,如上圖所述,refs的使用非常簡單,因為每個元件例項都有一個this.refs
屬性,會自動引用所有包含ref屬性元件的DOM,所以我們只需要在目標元件上新增一個自定義的ref
,然後進行使用即可:
class Button extends Component {
constructor(props){
super(props);
}
componentDidMount = () => {
let btn = this.refs.btn;
let link = this.refs.link;
}
render(){
return (
<div>
<button ref='btn'>click</button>
<a href='facebook.github.io/react' ref='link'>click</a>
</div>
)
}
}
複製程式碼
文件加粗部分提醒到,將會在未來的某個版本把這種用法完全移除掉,建議開發者升級版本後使用新的ref。那麼新的ref如何使用呢?
第一個重點是將ref改為回撥函式的方式去使用。
直接上程式碼:
class Input extends Component {
constructor(props){
super(props);
}
focus = () => {
this.textInput.focus();
}
render(){
return (
<div>
<input ref={(input) => { this.textInput = input }} />
</div>
)
}
}
複製程式碼
這裡我們可能就有第一個疑問了,input
引數是哪來的?文件中這樣解釋:
這就說明,當我們在DOM Element中使用ref
時,回撥函式將接收當前的DOM元素作為引數,然後儲存一個指向這個DOM元素的引用。那麼在示例程式碼中,我們已經把input
元素儲存在了this.textInput
中,在focus
函式中直接使用原生DOM API實現focus聚焦。
那麼第二個疑問出現了,回撥函式什麼時候被呼叫?
答案是當元件掛載後和解除安裝後,以及ref屬性本身發生變化時,回撥函式就會被呼叫。
第二個重點是,可以在元件例項中使用`ref`。
前面的示例程式碼是在DOM新增ref
屬性,那麼我們來看看如何在元件例項中使用。再上程式碼:
//<Input>來源於上面的示例程式碼?
class AutoFocusTextInput extends Component {
componentDidMount(){
this.textInput.focus();
}
render(){
return (
<Input ref={(input) => { this.textInput = input }}>
)
}
}
複製程式碼
當我們在<Input>
中新增ref
屬性時,其回撥函式接收已經掛載的元件例項<Input>
作為引數,並通過this.textInput
訪問到其內部的focus
方法。也就是說,上面的示例程式碼實現了當AutoFocusTextInput
元件掛載後<Input>
元件的自動聚焦。
接下來文件指出,<Input>
元件必須是使用class
宣告的元件,不然無法使用。這意味著React逐漸與ES6全面接軌了。
第三個重點,不能在無狀態元件中使用`ref`。
原因很簡單,因為ref
引用的是元件的例項,而無狀態元件準確的說是個函式元件(Functional Component),沒有例項。上程式碼:
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
render() {
return (
<MyFunctionalComponent
ref={(input) => { this.textInput = input; }} />
);
}
}
複製程式碼
上面的程式碼是無法正常工作的。
第四個重點,父元件的ref回撥函式可以使用子元件的DOM。
這是Facebook非常不推薦的做法,因為這樣會打破元件的封裝性,這種方法只是某些特殊場景下的權宜之計。我們看看如何實現,上程式碼:
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
複製程式碼
原理就是父元件把ref
的回撥函式當做inputRef
props傳遞給子元件,然後子元件<CustomTextInput>
把這個函式和當前的DOM繫結,最終的結果是父元件<Parent>
的this.inputElement
儲存的DOM是子元件<CustomTextInput>
中的input
。
同樣的道理,如果A元件是B元件的父元件,B元件是C元件的父元件,那麼可用上面的方法,讓A元件拿到C元件的DOM。但是官方態度是discouraged,這種多級呼叫確實不雅,我們確實需要考慮其他更好的方案了。
結語:
`refs`提供的是另一種與react傳統響應資料流完全不同的元件間互動方式,所以官方指出不要過度使用`refs`,而且從官方對它的態度來看,未來或許有更好的API來取代它。但目前來說`refs`仍是一個不錯的解決方案。最近社群對於React的改進建議越來越多,例如this.setState()
這樣的回撥函式到底是不是一個好方法,對於複雜程度高,數量多的元件如何高效地進行單元測試,大型應用對於大量state如何進行有效的管理,雖然有redux,mobx這樣優秀的解決方案,但如果react從根本設計上解決這一痛點,是否能再次對前端開發進行新一輪技術革命呢?
今天是2017年5月31日,四年前的5月30日,React正式釋出了。過去的四年是web技術發展最快的四年,無數新技術和新思想噴薄而出。下個四年,我們共同期待。