咱們知道react數據流是自上而下的,想要改變子組件的狀態只須要傳遞props,可是在某些特定場景下,咱們想獲取子組件實例或者dom元素,按咱們本身的方式來操做元素的行爲,這個時候就須要用到ref來獲取子組件或者dom元素html
場景: 動畫 或者 某個時候咱們 想focus 一個 input 輸入框node
1.能夠在dom元素上面使用ref屬性react
2.能夠在class組件上面使用ref屬性git
3.不許在函數組件上面使用ref屬性,緣由:函數組件沒有實例,父組件獲取子組件的ref,實際上是在獲取子組件的實例;(可是能夠在函數組件中 獲取子組件實例的ref,並用一個變量存儲起來,這個是沒有問題的)github
我的感受函數組件仍是有不少限制的,好比不能使用state和生命週期方法,也不能使用ref屬性(這個問題我在開發中遇到了,詳情見下文)api
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 對象app
2.render的時候 接收 子組件或者dom元素的ref屬性less
3.用this.inputRef.current 來獲取這個 子組件或者dom元素dom
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
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了)
其實 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了
<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. 複製代碼
null
, ref 會在 componentDidMount
或者 componentDidUpdate
以前更新