從React官方文件看 refs 的使用和未來

ssssyoki發表於2017-05-31

React先進的開發思想一直為社群所稱道,基於資料流的設計極大地簡化了前端開發成本。但如同官方文件所述,web開發中有很多場景需求是脫離資料流的,典型的如處理文字輸入框聚焦(focus)。為此React提供了refs供開發者使用。

筆者是在2015年下半年開始學習React,那時候官方文件關於refs的介紹和使用與現在完全不同。最新的React版本(v15.5.4)已經對這個API進行了修改並更新,不過我們仍可以在文件中找到老版本API的蛛絲馬跡:

從React官方文件看 refs 的使用和未來

我們來比較下新老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引數是哪來的?文件中這樣解釋:

從React官方文件看 refs 的使用和未來

這就說明,當我們在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的回撥函式當做inputRefprops傳遞給子元件,然後子元件<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技術發展最快的四年,無數新技術和新思想噴薄而出。下個四年,我們共同期待。

相關文章