1吊宋、定義
在React 數(shù)據(jù)流中,props是父組件與子組件交互的唯一方式颜武。需要修改子組件璃搜,要使用新的props來(lái)重新渲染。
但有些情況下鳞上,需要我們強(qiáng)制修改子組件(被修改的子組件可能是一個(gè) React 組件的實(shí)例这吻,也可能是一個(gè) DOM 元素),這時(shí)就可以使用Refs篙议。
Refs 提供了一種方式唾糯,允許我們?cè)L問(wèn) DOM 節(jié)點(diǎn)或在 render 方法中創(chuàng)建的 React 元素。
2鬼贱、使用場(chǎng)景
下面是幾個(gè)適合使用 refs 的情況:
1.管理焦點(diǎn)移怯,文本選擇或媒體播放。
2.觸發(fā)強(qiáng)制動(dòng)畫(huà)这难。
3.集成第三方 DOM 庫(kù)舟误。
4.避免使用 refs 來(lái)做任何可以通過(guò)聲明式實(shí)現(xiàn)來(lái)完成的事情。
3姻乓、Refs API使用
3.1 創(chuàng)建Refs
Refs 使用 React.createRef() 創(chuàng)建的嵌溢,并通過(guò) ref 屬性附加到 React 元素。(在構(gòu)造組件時(shí)糖权,通常將 Refs 分配給實(shí)例屬性堵腹,以便可以在整個(gè)組件中引用它們)
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
3.2 訪(fǎng)問(wèn) Refs
當(dāng) ref 被傳遞給 render 中的元素時(shí)炸站,對(duì)該節(jié)點(diǎn)的引用可以在 ref 的 current 屬性中被訪(fǎng)問(wèn)星澳。
console.log(this.myRef.current);//打印出設(shè)置了refs 的節(jié)點(diǎn)元素
ref 的值根據(jù)節(jié)點(diǎn)的類(lèi)型而有所不同:
一、當(dāng) ref 屬性用于 HTML 元素時(shí)旱易,構(gòu)造函數(shù)中使用 React.createRef() 創(chuàng)建的 ref 接收底層 DOM 元素作為其 current 屬性禁偎。
//使用React.createRef()創(chuàng)建refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 創(chuàng)建一個(gè) ref 來(lái)存儲(chǔ) textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 輸入框獲得焦點(diǎn)
// 注意:我們通過(guò) "current" 來(lái)訪(fǎng)問(wèn) DOM 節(jié)點(diǎn)
this.textInput.current.value = "";
this.textInput.current.focus();
}
render() {
// 告訴 React 我們想把 <input> ref 關(guān)聯(lián)到
// 構(gòu)造器里創(chuàng)建的 `textInput` 上
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
二、當(dāng) ref 屬性用于自定義 class 組件時(shí)阀坏,ref 對(duì)象接收組件的掛載實(shí)例作為其 current 屬性如暖。
//為 class 組件添加 Ref
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
//調(diào)用class組件的方法
this.textInput.current.focusTextInput();
}
render() {
return <CustomTextInput ref={this.textInput} />;
}
}
你不能在函數(shù)組件上使用 ref 屬性,因?yàn)樗麄儧](méi)有實(shí)例忌堂,可但以在函數(shù)組件內(nèi)部使用 ref 屬性盒至。
//使用React.createRef()創(chuàng)建refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 創(chuàng)建一個(gè) ref 來(lái)存儲(chǔ) textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 輸入框獲得焦點(diǎn)
// 注意:我們通過(guò) "current" 來(lái)訪(fǎng)問(wèn) DOM 節(jié)點(diǎn)
this.textInput.current.value = "";
this.textInput.current.focus();
}
render() {
// 告訴 React 我們想把 <input> ref 關(guān)聯(lián)到
// 構(gòu)造器里創(chuàng)建的 `textInput` 上
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
4、回調(diào) Refs
React 也支持另一種設(shè)置 refs 的方式,稱(chēng)為“回調(diào) refs”枷遂。它能助你更精細(xì)地控制何時(shí) refs 被設(shè)置和解除樱衷。
不同于傳遞 createRef() 創(chuàng)建的 ref 屬性,你會(huì)傳遞一個(gè)函數(shù)酒唉。這個(gè)函數(shù)中接受 React 組件實(shí)例或 HTML DOM 元素作為參數(shù)矩桂,以使它們能在其他地方被存儲(chǔ)和訪(fǎng)問(wèn)。
//回調(diào)Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = null;
}
componentDidMount() {
this.myRef.innerHTML = "我是回調(diào)Refs";
console.log(this.myRef);
}
render() {
return (
<div
ref={element => {
this.myRef = element;
}}
/>
);
}
}
可以在組件間傳遞回調(diào)形式的 refs痪伦,就像你可以傳遞通過(guò) React.createRef() 創(chuàng)建的對(duì)象 refs 一樣侄榴。
function MyComponent(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<MyComponent
inputRef={el => this.inputElement = el}
/>
);
}
}
5、String 類(lèi)型的 Refs(老版本React中的API网沾,已經(jīng)過(guò)時(shí)癞蚕,不建議使用)
//過(guò)時(shí)的Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.refs.myRef.innerHTML = "我是過(guò)時(shí)的String 類(lèi)型的Refs";//通過(guò) this.refs.XXXX來(lái)訪(fǎng)問(wèn) DOM 節(jié)點(diǎn)
console.log(this.refs.myRef);
}
render() {
return <div ref="myRef" />;
}
}
6、將 DOM Refs 暴露給父組件
在極少數(shù)情況下绅这,你可能希望在父組件中引用子節(jié)點(diǎn)的 DOM 節(jié)點(diǎn)涣达。通常不建議這樣做,因?yàn)樗鼤?huì)打破組件的封裝证薇。在新版本中可以使用Refs 轉(zhuǎn)發(fā)度苔,老版本可以將refs作為一個(gè)參數(shù),放在props中直接傳遞(如下例)
function MyComponent(props) {//作為props的一員直接傳遞
return (
<div>
<input
ref={props.inputRef}
onChange={null}
/>
</div>
);
}
6.1 Refs 轉(zhuǎn)發(fā)
Ref 轉(zhuǎn)發(fā)是一項(xiàng)將 refs自動(dòng)地通過(guò)組件傳遞到其一子組件的技巧浑度。對(duì)于可重用的組件庫(kù)是很有用的寇窑。
我們需要使用API React.forwardRef 來(lái)獲取傳遞給它的 ref,然后轉(zhuǎn)發(fā)到它渲染的 DOM 箩张。
const MyButton = React.forwardRef((props, ref) => (
<button ref={ref} >
{props.children}
</button>
));
這樣甩骏,使用 MyButton 的組件可以獲取底層 DOM 節(jié)點(diǎn) button 的 ref ,并在必要時(shí)訪(fǎng)問(wèn)先慷,就像其直接使用 DOM button 一樣饮笛。
注意:
第二個(gè)參數(shù) ref 只在使用 React.forwardRef 定義組件時(shí)存在。常規(guī)函數(shù)和 class 組件不接收 ref 參數(shù)论熙,且 props 中也不存在 ref福青。
Ref 轉(zhuǎn)發(fā)不僅限于 DOM 組件,你也可以轉(zhuǎn)發(fā) refs 到 class 組件實(shí)例中脓诡。
class App extends React.Component {
constructor(props) {
super(props);
// 你可以直接獲取 DOM button 的 ref:
this.ref = React.createRef();
}
componentDidMount() {
setTimeout(() => {
this.ref.current.innerText = "Refs 轉(zhuǎn)發(fā)";
}, 2000);
console.log(this.ref.current);
}
render() {
return (
<div>
<MyButton ref={this.ref}>Click me!</MyButton>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
如圖无午,在父組件打印出了refs內(nèi)容,并且2秒改了了button上的內(nèi)容祝谚。
6.2 在高階組件中轉(zhuǎn)發(fā) refs
利用高階組件宪迟,就可以轉(zhuǎn)發(fā) refs 到 class 組件實(shí)例中。什么是高階組件呢交惯?
高階組件是一個(gè)函數(shù)次泽,它接受一個(gè)組件并返回一個(gè)新組件穿仪。
class TextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
setInputValue(value){
this.textInput.current.value=value;
}
render() {
return (<div><input ref={this.textInput} /></div>);
}
}
class App extends React.Component {
constructor(props) {
super(props);
// 你可以直接獲取 DOM button 的 ref:
this.ref = React.createRef();
this.HOCref = React.createRef();
}
componentDidMount() {
setTimeout(() => {
this.ref.current.innerText = "Refs 轉(zhuǎn)發(fā)";
}, 2000);
console.log(this.ref.current);
console.log(this.HOCref.current);
this.HOCref.current.setInputValue('我是父組件設(shè)置的值');
}
render() {
return (
<div>
<MyButton ref={this.ref}>Click me!</MyButton>
<NewFancyButton ref={this.HOCref}>高階函數(shù)</NewFancyButton>
</div>
);
}
}
const NewFancyButton = HOCFunc(TextInput);
function HOCFunc(Component) {
class HOCComponent extends React.Component {
componentDidUpdate(prevProps) {
console.log("old props:", prevProps);
console.log("new props:", this.props);
}
render() {
const { forwardedRef, ...rest } = this.props;
// 將自定義的 prop 屬性 “forwardedRef” 定義為 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回調(diào)的第二個(gè)參數(shù) “ref”。
// 我們可以將其作為常規(guī) prop 屬性傳遞給 HOCComponent意荤,例如 “forwardedRef”
// 然后它就可以被掛載到被 HOCComponent 包裹的子組件上牡借。
return React.forwardRef((props, ref) => {
return <HOCComponent {...props} forwardedRef={ref} />;
});
}
上面的示例有一點(diǎn)需要注意:refs將不會(huì)透?jìng)飨氯ァ_@是因?yàn)閞ef不是prop屬性袭异。就像key一樣钠龙,其被React進(jìn)行了特殊處理。如果你對(duì)HOC添加ref御铃,該ref將引用最外層的容器組件碴里,而不是被包裹的組件。
幸運(yùn)的是上真,我們可以使用React.forwardRefAPI明確地將refs轉(zhuǎn)發(fā)到內(nèi)部的TextInput組件咬腋。React.forwardRef接受一個(gè)渲染函數(shù),其接收props和ref參數(shù)并返回一個(gè)React節(jié)點(diǎn)睡互。
上圖中打印出了轉(zhuǎn)發(fā)后得到refs的Class組件TextInput根竿,并且在父組件中操作了子組件中的方法。