1. 程式人生 > 其它 >React —— 使用Refs轉發技巧

React —— 使用Refs轉發技巧

技術標籤:個人學習Reactreact

Refs轉發

Ref轉發是一項將ref自動地通過元件傳遞到其一子元件的技巧。對於大多數應用中的元件來說,這通常不是必需的。但是其對某些元件,尤其是可重用的元件庫是很有用的。

轉發refs到DOM元件

考慮這個渲染原生DOM元素button的FancyButton元件:

function FancyButton(props){
    return (
        <button className="FancyButton">
            {props.children}
        </button>
    )
}

React元件隱藏其他實現細節,包括其渲染結果。其他使用FancyButton的元件通常不需要獲取內部的DOM元素button的ref。這樣可以防止元件過度依賴其他元件的DOM結構。
雖然這種封裝對於類似FeedStoryComments這樣的應用級元件是理想的,但其對FancyButtonMyTextInput這樣的高可複用“葉”元件來說可能是不方便的。這些元件傾向於在整個應用中以一種類似常規DOMbuttoninput的方式被使用,並且訪問其DOM節點對管理焦點,選中或動畫來說是不可避免的。
Ref轉發是一個可選特性,其允許某些元件接受ref,並將其向下傳遞(換句話說,轉發發它)給子元件


在下面的例項中,FancyButton使用React.forwardRef來獲取傳遞給它的ref,然後轉發到它渲染的DOMbutton:

const FancyButton = React.forwardRef((props,ref)=>(
    <button ref={ref} className="FancyButton">
        {props.children}
    </button>
))
// 你可以直接獲取DOM button 的ref
const ref = React.createRef();
<FancyButton ref="ref">click me!</FancyButton>

這樣,使用FancyButton的元件可以獲取底層DOM節點的ref,並在必要時訪問,就像其直接使用DOMbuttom一樣。
逐步解析:

  1. 我們通過呼叫React.createRef()建立了一個React Ref並將其賦值給了ref變數。
  2. 我們通過指定ref為了JSX屬性,將其向下傳遞給<FancyButton ref={ref}/>
  3. React傳遞ref給forwardRef內函式(props,ref)=>...,座位其第二個引數。
  4. 我們向下轉發該ref引數到<button ref={ref}>,將其指定為JSX屬性。
  5. 當ref掛載完成。ref.current將指向<button>DON節點。
    注意:第二個引數ref只在使用React。forwardRef定義元件時存在。常規函式和class元件不接受ref引數,且props中也不存在ref
    Ref轉發不限於DOM元件,你也可以轉發refs到class元件例項中。

元件庫維護者的注意事項

當你開始在元件庫中使用forwardRef時,你應當將其視為一個破壞性更改,併發布庫的一個新的主版本。這是因為你的庫可能會有明顯不同的行為(例如refs被分配給了誰,以及匯出了什麼型別),並且這樣可能會導致依賴舊行為的應用和其他庫崩潰。
出於同樣的願意,當React.forwardRef存在時有條件的使用它也是不推薦的:它改變了你的庫的行為,並在升級React自身時破壞使用者的應用。

在高階元件中轉發refs

這個技巧對高階元件(也被成為H0C)特別有用。讓我們從一個輸出元件props到控制檯的HOC例項開始:

function logProps(WrappedComponent){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            return <WrappedComponent {...this.props} />
        }
    }

    return LogProps
}

“logProps”HOC透傳(pass through)所有的props到其包裹的元件,所以選結果將是相同的,例如,如果我們使用該HOC記錄所有傳遞到Fancybutton元件的props:

function logProps(WrappedComponent){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            return <WrappedComponent {...this.props} />
        }
    }

    return LogProps
}

class FancyButton extends React.Component{
    focus(){}
}

const MFancyButton = logProps(FancyButton);

上面示例有一點需要注意:refs將不會透傳下去。這是因為ref不是prop屬性。就像key一樣,其被React做了特殊處理,如果你對應HOC新增ref,該ref將引用最外層的容器元件,而不是包裹元件。
這意味著我們FancyButton元件的refs實際上被掛到LogProps元件:

const ref = React.createRef();
// 我們匯入的 FancyButton 元件是高階元件(HOC)LogProps。
// 儘管渲染結果將是一樣的,
// 但我們的 ref 將指向 LogProps 而不是內部的 FancyButton 元件!
// 這意味著我們不能呼叫例如 ref.current.focus() 這樣的方法
<MFancyButton label="click me" handleClick={handleClick} ref={ref} />

幸運的是,我們可以使用React.forwardRefAPI明確地將refs轉發到內部的FancyButton元件。React.forwardRef接受一個渲染函式,其接受propsref引數並返回一個React及誒點。例如:

function logProps(Component){
    class LogProps extends React.Component{
        componentDidUpdate(prevProps){
            console.log('old props:',prevProps)
            console.log('new props:',this.props)
        }
        render(){
            const {forwardedRef,...res} = this.props;
            return <Component {...res}  ref={forwardedRef} />
        }
    }

    return React.forwardRef((props,ref)=>{
            return  <LogProps {...props}  forwardRef={ref} />  
    })
}

在DevTools中顯示自定義名稱

React.forwardedRef接受一個渲染函式。React DevTools使用該函式來決定為ref轉發元件顯示的內容。
例如,以下元件將在DevTools中顯示為了ForwardRef

const WrappedComponent = React.forwardRef((props,ref)=>{
    return <LogProps  ref={ref} {...props}/>
})

如果你命名了渲染函式,devTools也將包含其名稱(例如’ForwardRef(myFunction)’):

const WrappedComponent = React.forwardRef(
    function myFunction(this.props,ref){
        return <LogProps {...props}  forwardRef={ref} />
    }
)

你甚至可以設定函式的displayName屬性來包含被包裹元件的名稱:

function logProps(Component){
    class LogProps extends React.Component{

    }
    function forwardRef(props,ref){
        return <LogProps {...props} forwardRef={ref} />
    }
    // 在DevTools中為該元件提供一個更有用的顯示名
    // 例如 "ForwardRef(logProps(MyComponent))"
    const name = Component.displayName || Component.name ;

    forwardRef.displayName = `logProps(${name})`;
    return React.forwardRef(forwardRef)
}