ref的作用
我們知道react資料流是自上而下的,想要改變子元件的狀態只需要傳遞props,但是在某些特定場景下,我們想獲取子元件例項或者dom元素,按我們自己的方式來操作元素的行為,這個時候就需要用到ref來獲取子元件或者dom元素
場景: 動畫 或者 某個時候我們 想focus 一個 input 輸入框
使用ref的三大原則:
1.可以在dom元素上面使用ref屬性
2.可以在class元件上面使用ref屬性
3.不準在函式元件上面使用ref屬性,原因:函式元件沒有例項,父元件獲取子元件的ref,其實是在獲取子元件的例項;(但是可以在函式元件中 獲取子元件例項的ref,並用一個變數儲存起來,這個是沒有問題的)
個人感覺函式元件還是有很多限制的,比如不能使用state和生命週期方法,也不能使用ref屬性(這個問題我在開發中遇到了,詳情見下文)
基本使用
class App extends React.Component {
constructor() {
super();
this.inputRef = React.createRef();
}
handleClick = () => {
this.inputRef.current.focus();
};
render() {
return (
<div className="App">
<button onClick={this.handleClick}>點我 進行focus</button>
<input ref={this.inputRef} />
</div>
);
}
}
複製程式碼
此處示例是react16.3+版本的使用(如果想使用舊版本請看下文),步驟有三個:
1.createRef方法 生成ref 物件
2.render的時候 接收 子元件或者dom元素的ref屬性
3.用this.inputRef.current 來獲取這個 子元件或者dom元素
回撥的方式使用 ref
react16.2及以下一般用它
componentDidMount() {
if (this.img && this.img.complete) { //此處可以獲取到 img 這個dom 元素
this.onLoad()
}
}
render() {
const {className, src, alt, onClick, onRef, preview} = this.props
return (
{preview && !src ? (
<div>
<DefaultImageIcon />
</div>
) : (
<img
ref={node => {
this.img = node
if (onRef) {
onRef()
}
}}
src={src}
alt={alt}
onLoad={this.onLoad}
onClick={onClick}
/>
)}
)
}
複製程式碼
可以看到, 我們也可以直接使用回撥的方式來獲取ref,其實個人感覺這種方式更簡單,但是至於作者為什麼放棄了這種方式,我目前還不清楚。~我覺得是因為跨元件傳遞很麻煩吧~。 我們還看到,程式碼中,onRef方法可以直接把 ref傳給上層元件,那麼,如果想使用 新版本api將 ref傳遞給上層元件,有什麼辦法呢? 請看 forwardRef
forwardRef
const MyInput = React.forwardRef((props, ref) => (
<div>
我是 自定義input 元件
<input ref={ref}/>
</div>
))
class App extends React.Component {
constructor() {
super();
this.inputRef = React.createRef();
}
handleClick = () => {
this.inputRef.current.focus();
};
render() {
return (
<div className="App">
<button onClick={this.handleClick}>點我 進行focus</button>
<MyInput ref={this.inputRef}/>
</div>
);
}
}
複製程式碼
如果想在 上層元件中獲取 下層元件內部的 dom 元素或者子孫元件例項,那麼可以使用 React.forwardRef 給元件包裹一層. 執行順序是: 上層元件 createRef => App 元件傳遞 ref 給 MyInput => MyInput 通過 forwardRef 方法 接收到 ref => input 輸入框 將節點 傳給ref(此時這個ref已經是上層元件的ref了)
高階元件中 使用 ref
其實 forwardRef 使用到的場景不多, 但是有時候 確實需要跨元件傳遞 ref, 那就只能通過它來實現了。 我們知道, 為了實現一些邏輯,我們會使用高階元件,它其實就是給一個元件封裝一層,但是如果我們想在上層元件獲取ref,獲取的是高階元件的ref, 為什麼?舉個 例子: 最上層元件 叫 App, 高階元件叫 MyHOC, 子元件叫 MyInput, 我們想在App 中 獲取 這個MyInput, 那麼如下使用是錯誤的:
如下例子,App裡面接收到ref 其實是Foo 的ref,然而 Foo 沒有ref
// MyInput.js
import MyHOC from './MyHOC'
const MyInput = () => (
<div>
我是 自定義input 元件
<input />
</div>
)
return MyHOC(MyInput)
// MyHOC.js
const MyHOC = (WrappedComponent) => {
// do sth here..
class Foo extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
return <Foo />;
}
export default MyHOC
//App.js
import MyInput from './MyInput';
const ref = React.createRef();
<MyInput
ref={ref} // 此處的 ref其實是 Foo
/>;
複製程式碼
那麼應該如何正確地在Hoc中使用呢?
// MyInput.js
import MyHOC from './MyHOC'
const MyInput = () => (
<div>
我是 自定義input 元件
<input />
</div>
)
return MyHOC(MyInput)
// MyHOC
const MyHOC = (WrappedComponent) => {
class Foo extends React.Component {
render() {
const { forwardedRef, ...rest } = this.props;
// 此時 ref 便是這個WrappedComponent
return <WrappedComponent ref={forwardedRef} {...rest} />;
}
}
// 用React.forwardRef 包裹一層,這樣將 上層元件的ref 命名為 forwardedRef (任意定義的一個屬性名) 傳遞給子元件
return React.forwardRef((props, ref) => {
return <Foo {...props} forwardedRef={ref} />;
});
}
export default MyHOC
//App.js
import MyInput from './MyInput';
const ref = React.createRef();
<MyInput
ref={ref}
/>;
複製程式碼
如上程式碼所示, 唯一一個不同的地方是: MyHOC
元件使用 React.forwardRef
將ref 向下層傳遞, 這樣外層元件接收到的ref 就是真實的 MyInput 元件,而非 Foo了
問題&解決方法
- 函式式元件不能使用ref屬性(這個問題我在開發中也遇到了,使用ant design 的 getFieldDecorator()(myFnComponent)時 會報 “無法掛載ref的”錯誤)
<Form.Item
label={field.label}
key={`key_${field.label}`}
>
{render
? render()
: getFieldDecorator(field.fieldName, {
rules: R.pathOr([], ['config', 'rules'], field),
initialValue: data[field.field],
})(<Item {...field}/>)} // 此處的Item是個 函式式元件, 這樣寫會出錯
// })(Item(field))} 這樣寫才是ok的, 因為Item會返回一個類元件
</Form.Item>
// 報錯資訊: Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
複製程式碼
- 元件中多個ref 怎麼辦? 那麼就多次建立 ref
- 需要注意: 如果 在元件掛載 之前獲取 this.myRef.current, 那麼會返回一個
null
, ref 會在componentDidMount
或者componentDidUpdate
之前更新