React —— 使用Refs轉發技巧
Refs轉發
Ref轉發是一項將ref自動地通過元件傳遞到其一子元件的技巧。對於大多數應用中的元件來說,這通常不是必需的。但是其對某些元件,尤其是可重用的元件庫是很有用的。
轉發refs到DOM元件
考慮這個渲染原生DOM元素button的FancyButton元件:
function FancyButton(props){ return ( <button className="FancyButton"> {props.children} </button> ) }
React元件隱藏其他實現細節,包括其渲染結果。其他使用FancyButton
的元件通常不需要獲取內部的DOM元素button
的ref。這樣可以防止元件過度依賴其他元件的DOM結構。
雖然這種封裝對於類似FeedStory
或Comments
這樣的應用級元件是理想的,但其對FancyButton
或MyTextInput
這樣的高可複用“葉”元件來說可能是不方便的。這些元件傾向於在整個應用中以一種類似常規DOMbutton
和input
的方式被使用,並且訪問其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一樣。
逐步解析:
- 我們通過呼叫
React.createRef()
建立了一個React Ref
並將其賦值給了ref
變數。 - 我們通過指定
ref
為了JSX屬性,將其向下傳遞給<FancyButton ref={ref}/>
。 - React傳遞ref給
forwardRef
內函式(props,ref)=>...
,座位其第二個引數。 - 我們向下轉發該
ref
引數到<button ref={ref}>
,將其指定為JSX屬性。 - 當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.forwardRef
API明確地將refs轉發到內部的FancyButton
元件。React.forwardRef
接受一個渲染函式,其接受props
和ref
引數並返回一個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)
}