從React官方文檔看 refs 的使用和將來

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

筆者是在2015年下半年開始學習React,那時候官方文檔關於refs的介紹和使用與如今徹底不一樣。最新的React版本(v15.5.4)已經對這個API進行了修改並更新,不過咱們仍能夠在文檔中找到老版本API的蛛絲馬跡:react

咱們來比較下新老refs有哪些異同:git

在舊版本中,如上圖所述,refs的使用很是簡單,由於每一個組件實例都有一個this.refs屬性,會自動引用全部包含ref屬性組件的DOM,因此咱們只須要在目標組件上添加一個自定義的ref,而後進行使用便可:github

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如何使用呢?web

第一個重點是將ref改成回調函數的方式去使用。

直接上代碼:redux

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聚焦。bash

那麼第二個疑問出現了,回調函數何時被調用?函數

答案是當組件掛載後和卸載後,以及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技術發展最快的四年,無數新技術和新思想噴薄而出。下個四年,咱們共同期待。

相關文章
相關標籤/搜索