React出現後,提供了state, props,前端開發者無須在直接操作dom。React官方不推薦我們直接訪問、操作DOM,但是,還是為我們留了一個後門ref,方便訪問操作DOM。因為在某些特定的場景,必須使用ref來訪問DOM元素。比如:input的focus,媒體播放器、元件的位置,動畫,引入第三dom方庫。
建立和獲取ref
在reactd的版本歷史上,出現了三種建立ref的方式:string ref,callback ref,React.createRef。無論哪種方式,都是為ref屬性賦值,ref和key一樣,都是關鍵字,為React內部使用。 另外,值得注意的是,所有的ref獲取,最好在元件載入結束之後,否則無法獲取值。因為元件載入後,dom才準備好了是吧。
string ref
class Test extends React.Component {
componentDidMount(){
// 獲取
console.log(this.refs.first);
// <input value="first">
}
render() {
// 建立
return <input value="first" ref="first" />
}
}
複製程式碼
string ref建立的ref的方法在React16.3之後的版本棄用了,並且官方表示,在16.3之前,儘量使用callback ref來建立ref。因為string ref建立的ref帶有些問題,具體原因見連線。
callback ref
class Test extends React.Component {
componentDidMount(){
// 獲取
console.log(this.second);
// <input value="second">
}
render() {
// 建立
return <input value="second" ref={(input) => {this.second = input }} />
}
}
複製程式碼
通過回撥函式的方式建立ref,形勢上看上去稍微有些繁瑣。並且,callback以上面這種內聯的方式賦值,在元件發生了更新時,ref都會重新建立。可通過將這個回撥函式變成類的方法來避免。
class Test extends React.Component {
componentDidMount(){
// 獲取
console.log(this.second);
// <input value="second">
}
createRef = (dom) => {
this.second = dom;
}
render() {
// 建立
return <input value="second" ref={this.createRef} />
}
}
複製程式碼
React.createRef
class Test extends React.Component {
constructor(props) {
super(props);
// 建立
this.third = React.createRef();
}
componentDidMount(){
// 獲取
console.log(this.third.current);
// <input value="third">
}
render() {
// 賦值
return <input value="third" ref={this.third} />
}
}
複製程式碼
React.ref是React16.3後新加的一個建立ref的方法,寫法相對於callback ref的寫法相對簡潔。將建立的ref賦值給不同的子元素,ref的current的值有所區別。
不同子元素的ref值
這裡討論通過callback ref和React.createRef建立的ref,賦值給不同的子元素後,ref的取值的不同。
HTML元素
為HTML元素的ref賦值,獲取到的ref的值為這個HTML元素對應的DOM元素。
class Test extends React.Component {
second = React.creteRef()
handleSubmit = () => {
console.log(this.first);
// <input value="first">
console.log(this.second.current);
// <input value="second">
}
render() {
// 建立
return (
<div>
<input value="first" ref={(input) => {this.first = input} } />
<input value="second" ref={this.second} />
<button onClick={this.handleSubmit} >提交</button>
</div>
)
}
}
複製程式碼
class類建立的React元件
為class類建立的React元件的ref賦值後,最終獲取到的值為這個React元件的例項。
class Test extends React.Component {
second = React.createRef()
handleSubmit = () => {
console.log(this.first);
// Hello {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}
console.log(this.second.current);
// Hello {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}
}
render() {
return (
<div>
<Hello ref={(input) => {this.first = input}} />
<Hello ref={this.second} />
<button onClick={this.handleSubmit}>submit</button>
</div>
)
}
}
複製程式碼
function建立的React元件
遺憾的是,兩種方法均無法為function建立的React元件ref賦值,就算賦值,獲取到的最終結果為null。
進階的ref使用
通過react提供的ref,父元件可以獲取到具體的某個子元素,這是基本的用法。以下還將提到一些進階用法。
React.forwardRef
這裡有個問題存在,有沒有辦法穿過父元件,獲取子元素?正好,與React.createRef一起出世的還有一個用於解決這個問題的直接辦法React.forwarRef。其實,這個這個問題主要出現在HOC高階元件上,開發者可以通過React.forwardRef獲取WrapperedComponnet,而不是外面的包裹層。
class FancyInput extends React.Component{
render() {
return <input value="fancyInput" />
}
}
const HOCFn = (WrapperedComponent) => {
class Test extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return (
<WrapperedComponent ref={forwardRef} {...rest} />
)
}
}
return React.forwardRef((props, ref) => {
return <Test forwardRef={ref} {...props} />
});
}
const HocEdComp = HOCFn(FancyInput);
export default class NewComp extends React.Component {
handleSubmit = () => {
console.log(this.testRef);
// FancyInput
}
render() {
return (
<div>
<HocEdComp ref={(dom) => { this.testRef = dom }} />
<button onClick={this.handleSubmit}>提交</button>
</div>
)
}
};
複製程式碼
最終,從上面的例子可知,this.testRef指向的是FancyInput,而非HocEdComp。
獲取資源子元素中指定的DOM
父元素可以通過ref機制獲取html元素,React元件例項,當然這兩者都是作為父元素的子元素存在。在這裡我們看到幾點侷限性:
- 無法獲取React元件中的某個dom元素
- 無法獲取function建立的元件
在這裡,可以使用一些小技巧。看例子:
const First = (props) => {
return <input value="first" ref={props.firstRef} />
}
class Second extends React.Component {
render() {
return <input value="second" ref={this.props.secondRef} />;
}
}
export default class Test extends React.Component {
handleSubmit = () => {
console.log(this.first);
// <input value="first">
console.log(this.second)
// <input value="second">
}
render() {
return (
<div>
<First firstRef={(input) => { this.first = input }} />
<Second secondRef={(input) => { this.second = input }} />
<button onClick={this.handleSubmit}>submit</button>
</div>
)
}
}
複製程式碼
這種方法的本質是,將建立的ref作為props傳遞給React元件,React元件的某個HTML元素的ref接收這個屬性,父元素便可獲取到子元件中具體的某一個HTML元素的底層DOM。這樣做,破壞了元件的封裝性,但是有時萬不得已只能這麼做了。
總結
React提供ref的初衷是給開發者一個可獲取實際DOM的工具,目前,我們也可以通過ref獲取子元件(React元件),但是,React還是提倡不要過度使用ref,,畢竟有了state,props,我們已經從各種繁雜的DOM操作中解放出來。在工作中,有時候設計到動畫,或者媒體播放器,input聚焦時,我會使用以下ref,確實解決了state和props無法解決的問題。
我只是總結可以如何使用ref,並沒有細細瞭解ref的原理,找個機會希望可以一探究竟,又當了一次搬運工。