Refs提供了一個訪問render()方法內(nèi)DOM節(jié)點或者ReactNode的方法
典型的React數(shù)據(jù)流中,props是父組件和子組件交互的唯一手段。要修改一個子組件,就需要使用新的props重新渲染它。然而,確實存在少數(shù)一些情況需要命令性地(imperatively)修改一個子節(jié)點而不是通過典型的props數(shù)據(jù)流方式。被修改的子節(jié)點可能是一個React組件實例(比如調(diào)用某個子組件的實例方法),亦或是一個DOM元素(比如手動地控制某個input標(biāo)簽聚焦)。對于這兩種情況,React都提供了各種處理方法,refs就是其中的一種。
1. refs適用的場景
- 手動控制DOM節(jié)點
- 獲取子組件的尺寸或者實例方法
- 和第三方DOM庫集成(典型的jPlayer)
注意:不要濫用refs,比如:在使用antd的<Modal />時,可以直接通過修改props.visible為true或false即可實現(xiàn)Modal組件的顯示和隱藏,則大可不必使用該組件的show()、hide()方法
2. 創(chuàng)建Refs
注意:Ract 16.3引入的API React.createRef()。比較舊的React版本,保留了refs關(guān)鍵字。不管是新舊版本,都建議使用refs回調(diào)方式(最后的有對應(yīng)的示例)。本文主要實現(xiàn)一個
頁面加載時,Input組件自動聚焦,并且在點擊Button組件時聚焦Input組件的功能,使用的方法為React.createRef()。github代碼庫中有對應(yīng)的新舊版本實現(xiàn)方式。
- refs通過React.createRef()創(chuàng)建,使屬性ref附加到React元素上。Refs通常在
一個組件構(gòu)造時賦值給一個實例屬性,這樣在整個組件中他們都可以被引用到。 - 創(chuàng)建以后,通過React.createRef().current方法獲取。
根據(jù)節(jié)點類型的不同,ref的值也不同:
- 如果ref用在HTML元素上,構(gòu)造函數(shù)中通過React.createRef()創(chuàng)建的ref會將原生DOM元素放到它的current屬性中。
- 如果ref用在自定義組件類型上,ref使用它的current屬性指向所掛載的組件實例。
- 函數(shù)式組件上不能使用ref,因為它們沒有實例。
3. DOM中創(chuàng)建與使用
class Input extends React.Component {
constructor(props) {
super(props)
// 在構(gòu)造方法內(nèi)初始化
this.inputRef = React.createRef()
}
componentDidMount() {
// 使用.current調(diào)用
this.inputRef.current.focus();
}
// Input的實例方法
focus = () => {
if(this.inputRef.current) this.inputRef.current.focus();
}
render() {
return (
<div className="block">
<p>Input 加載時自動聚焦</p>
<input ref={this.inputRef} />
</div>
)
}
}
組件掛載時,React會將ref的current屬性設(shè)置成DOM元素,卸載時,再把ref的current屬性設(shè)置為null。ref更新發(fā)生在componentDidMount或者componentDidUpdate生命周期回調(diào)之前。
4. 自定義組件中創(chuàng)建與使用
import React from 'react'
import Button from './Button'
import Input from './Input'
class Ref extends React.Component {
constructor(props) {
super(props)
// 初始化 獲取掛載的組件Input實例
this.inputComponentRef = React.createRef()
}
handleClick = () => {
// 調(diào)用Input實例的方法
if(this.inputComponentRef.current) this.inputComponentRef.current.focus()
}
render() {
return (
<div>
<Button onClick={this.handleClick} />
<Input ref={this.inputComponentRef} />
</div>
)
}
}
export default Ref
同DOM中使用類似,組件掛載時,React會將ref的current屬性設(shè)置成組件的實例,卸載時,再把ref的current屬性設(shè)置為null。ref更新發(fā)生在componentDidMount或者componentDidUpdate生命周期回調(diào)之前。
5. 函數(shù)式組件無法為當(dāng)前組件直接創(chuàng)建refs
const Input = () => <input />
class App extends React.Component {
constructor(props) {
super(props);
this.inputComponentRef = React.createRef();
}
render() {
// 不起作用,會報錯
return (
<Input ref={this.inputComponentRef} />
);
}
}
但是,函數(shù)式組件內(nèi)部可以使用ref引用屬性使其指向一個DOM元素或者一個類組件,例如:
const Input = (props) => {
let inputRef = React.createRef();
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input
type="text"
ref={inputRef} />
<button
onClick={handleClick}
>Focus</button>
</div>
)
}
6. 使用回調(diào)的方式 (推薦)
在需要聲明ref的位置綁定一個方法,返回的參數(shù)是DOM節(jié)點或則實例組件,組件在加載時會自動觸發(fā)該回調(diào)方法,該參數(shù)作為實例的一個屬性在其他位置直接使用即可。
class Input extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
// 不需要使用current調(diào)用
this.inputRef && this.inputRef.focus();
}
initRef = (ele) => {
// 組件加載時(或者更新時)自動觸發(fā)該方法
this.inputRef = ele
}
focus = () => {
if(this.inputRef) this.inputRef.focus();
}
render() {
return (
<div className="block">
<p>Input 加載時自動聚焦</p>
<input ref={this.initRef} />
</div>
)
}
}
export default Input
使用引用回調(diào)函數(shù)的注意事項
如果ref回調(diào)函數(shù)定義在內(nèi)聯(lián)函數(shù)(inline function)中,更新時他會被調(diào)用兩次,第一次參數(shù)是null,第二次參數(shù)才是DOM元素。這是因為每個渲染都會創(chuàng)建一個新的函數(shù)實例,所以React需要清除舊的引用并設(shè)置新的。你可以通過將引用回調(diào)定義為該類的綁定方法來避免這種情況,但請注意,大多數(shù)情況下這樣做或者不這樣做都沒太大關(guān)系。
7. React低版本遺留的API:字符串引用Refs
綁定一個 字符串類型的ref 屬性到 render 的返回值上
<input ref="myInput" />
在其他位置(實例方法或者生命周期函數(shù)中)使用
componentDidMount() {
// 保留關(guān)鍵字this.refs
// 頁面加載完成時使input標(biāo)簽自動聚焦
this.refs.myInput.focus()
}
8. 建議使用其他的解決方案替代refs
在極少的一些情況下,我們需要從父組件中訪問某個子DOM節(jié)點或者子組件的一些屬性和方法。一般來說不建議這么做,因為它打破了組件封裝,但是它偶爾也很有用,比如觸發(fā)獲取焦點,或者測量一個子DOM節(jié)點的尺寸或者位置。
本文代碼鏈接地址:https://github.com/zhiyuanMain/ReactForJianshu.git