咱們在平常寫React代碼的時候,通常狀況是用不到Refs這個東西,由於咱們並不直接操做底層DOM元素,而是在render函數裏去編寫咱們的頁面結構,由React來組織DOM元素的更新。node
凡事總有例外,總會有一些很奇葩的時候咱們須要直接去操做頁面的真實DOM,這就要求咱們有直接訪問真實DOM的能力,而Refs就是爲咱們提供了這樣的能力。數組
看這個名字也知道,Refs實際上是提供了一個對真實DOM(組件)的引用,咱們能夠經過這個引用直接去操做DOM(組件)app
上面有提到,咱們通常狀況下是不須要用到這個東西,那具體何時纔會用到呢? 看官方建議:ide
- Managing focus, text selection, or media playback. - Triggering imperative animations. - Integrating with third-party DOM libraries.
簡單的來講就是處理DOM元素的focus,文本的選擇或者媒體的播放等,以及處罰強制動畫或者同第三方DOM庫集成的時候。函數
也就是React沒法控制局面的時候,就須要直接操做Refs了。動畫
咱們通常都是經過一個回調函數的方式,把當前組件的DOM綁定到一個實例變量上,像下面這樣:this
class AutoFocusTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; } componentDidMount() { this.textInput.focusTextInput(); } render() { return ( <CustomTextInput ref={ele => { this.textInput = ele}} /> ); } }
在上面的代碼中,咱們先聲明一個值爲null的textInput變量,而後在ref中以回調的方式將組件DOM賦值給textInput。而後就能夠經過 this.textInput.focus()這樣的性質來直接調用CustomTextInput這個組件的實例方法。rest
可是這個方式有如下兩個不太好:code
由於在每次渲染中React都會建立一個新的函數實例。所以,React 須要清理舊的 ref 而且設置新的。
經過將 ref 的回調函數定義成類的綁定函數的方式能夠避免上述問題,component
React V16版本新增一個API:React.createRef(); 經過這個API,咱們能夠先建立一個ref變量,而後再將這個變量賦值給組件聲明中ref屬性就行了。
具體看代碼:
class CustomTextInput extends React.Component { constructor(props) { super(props); // create a ref to store the textInput DOM element this.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 node this.textInput.current.focus(); } render() { // tell React that we want to associate the <input> ref // with the `textInput` that we created in the constructor return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
在上面的代碼中,咱們先經過 React.createRef();建立一個ref,並賦值給組件屬性textInput(this.textInput),而後在render函數中經過ref={this.textInput}的方式將ref和input這個真實DOM聯繫起來, 這樣就能夠經過 this.textInput.current.focus();的方式來直接操做input元素的方法。
在V16版本前,咱們能夠直接經過變量訪問元素的方法,在V16後,咱們須要經過 this.textInput.current,即真實的DOM是經過current屬性來引用的。
若是經過 createRef()這個API賦值給組件的ref,那麼引用的就是組件實例;若是是DOM元素,那引用的天然的就是DOM元素了。。
前面咱們說到,在V16版本以前,咱們想要父組件拿到子組件的ref,須要經過一些特殊的方法,V16版本以後,React提供了一種原生的方式來完成這種操做。
這就涉及到React新增的另外一個API: React.forwardRef(), 經過接受一個函數,來傳遞refs,具體以下:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // You can now get a ref directly to the DOM button: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
上面的有點繞,簡單來講,就是咱們建立一個引用,原本是給外面的FancyButton組件的,可是由於React.forwardRef的處理,這個引用被傳遞給了內部的button元素。這樣ref.current的引用由原本的FancyButton實例傳遞到了button元素自己。
HOC(higher-order components)高階組件,簡單的說,就是經過組件包裹的方式來提到代碼複用,高階組件就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件。
如下是一個生成高階組件的函數:
function logProps(WrappedComponent) { class LogProps extends React.Component { render() { return <WrappedComponent {...this.props} />; } } return LogProps; }
logProps是函數,接受一個組件參數,返回一個包裹參數組件的logProps組件。
下面是用法:
class FancyButton extends React.Component { focus() { // ... } // ... } // Rather than exporting FancyButton, we export LogProps. // It will render a FancyButton though. export default logProps(FancyButton);
咱們先聲明一個FancyButton的組件,而後將其做爲參數傳入logProps函數,最後獲得的實際上是一個LogProps組件。
接下來咱們使用refs:
import FancyButton from './FancyButton'; const ref = React.createRef(); // The FancyButton component we imported is the LogProps HOC. // Even though the rendered output will be the same, // Our ref will point to LogProps instead of the inner FancyButton component! // This means we can't call e.g. ref.current.focus() <FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
咱們經過文件引入FancyButton(其實引入的是LogProps組件)而後createRef並指向FancyButton。 本意是但願引入真正的FancyButton組件,實際上引用的是 外層包裹組件LogProps組件。
咱們能夠經過如下改造來完善代碼:
function logProps(Component) { class LogProps extends React.Component { render() { const {forwardedRef, ...rest} = this.props; // Assign the custom prop "forwardedRef" as a ref return <Component ref={forwardedRef} {...rest} />; } } // Note the second param "ref" provided by React.forwardRef. // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef" // And it can then be attached to the Component. return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); }
如面的代碼所示,咱們修改了高階組件logProps函數的實現方式,在內部組件LogProps的render方法中,給被包裹組件(做爲參數傳入的組件)添加了來自props的ref。
最終返回的也是一個React.forwardRef處理過的組件,這個組件將ref傳遞到內部的props中去。
這樣,但咱們經過logProps(FancyButton)函數調用的時候,其實返回的是一個通過React.forwardRef處理的組件, 當經過
<FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
去添加ref的時候, 這個ref其實直接添加到了內部的LogProps組件的forwardedRef屬性上,而後在LogProps組件內部,又經過props屬性的方式被賦值了 被包裹組件(做爲參數的組件,也就是FancyButton組件)。這個傳遞其實通過了三次。。。。
總的來講,高階組件的ref實際上是經過React.forwardRef技術將ref傳遞到包裹組件logProps上,而後有經過屬性傳遞 傳遞到真正的FancyButton組件上,兩次傳遞才完成。。。。