在介紹 Refs 以前,咱們先來了解兩個概念:受控組件 和 不受控組件。react
在HTML中,表單元素(如 input、textarea、select)之類的表單元素一般能夠本身維護state,並根據用戶的輸入進行更新。而在React中,可變狀態(mutable state)一般保存在組件的 state 屬性中,而且只能經過 setState()來更新。 在此,咱們將 React的state做爲惟一的數據源,經過渲染表單的React組件來控制用戶輸入過程當中表單發送的操做。 這個「被React經過此種方式控制取值的表單輸入元素」被成爲受控組件。react-native
從字面意思來理解:不被React組件控制的組件。在受控制組件中,表單數據由 React組件處理。其替代方案是不受控制組件,其中表單數據由DOM自己處理。文件輸入標籤就是一個典型的不受控制組件,它的值只能由用戶設置,經過DOM自身提供的一些特性來獲取。數組
受控組件和不受控組件最大的區別就是前者自身維護的狀態值變化,能夠配合自身的change事件,很容易進行修改或者校驗用戶的輸入。微信
在React中 由於 Refs的出現使得 不受控制組件自身狀態值的維護變得容易了許多,接下來咱們就重點介紹一下 Refs的使用方式。app
Refs 是一個 獲取 DOM節點或 React元素實例的工具。在 React 中 Refs 提供了一種方式,容許用戶訪問DOM 節點或者在render方法中建立的React元素。ide
在 React單項數據流中,props是父子組件交互的惟一方式。要修改一個子組件,須要經過的新的props來從新渲染。 可是在某些狀況下,須要在數據流以外強制修改子組件。被修改的子組件多是一個React組件實例,也多是一個DOM元素。對於這兩種狀況,React 都經過 Refs的使用提供了具體的解決方案。函數
refs 一般適合在一下場景中使用:工具
避免使用 refs 去作任何能夠經過聲明式實現來完成的事情。例如,避免在Dialog、Loading、Alert等組件內部暴露 open(), show(), hide(),close()等方法,最好經過 isXX屬性的方式來控制。動畫
關於refs的使用有兩種方式: 1)經過 React.createRef() API【在React 16.3版本以後引入了】;2)在較早的版本中,咱們推薦使用 回調形式的refs。ui
class TestComp extends React.Component {
constructor(props) {
super(props);
this.tRef = React.createRef();
}
render() {
return (
<div ref={ this.tRef }></div>
)
}
}
複製代碼
以上代碼 建立了一個實例屬性 this.tRef, 並將其 傳遞給 DOM元素 div。後續對該節點的引用就能夠在ref的 current屬性中訪問。ref的值根據節點類型的不一樣結果也不一樣:
class TestComp extends React.Component {
constructor(props) {
super(props);
// 建立一個 ref 來存儲 DOM元素 input
this.textInput = React.createRef();
this.focusEvent = this.focusEvent.bind(this);
}
focusEvent() {
// 直接經過原生API訪問輸入框獲取焦點事件
this.textInput.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.textInput} />
<input type="button" value="獲取文本框焦點事件" onClick={this.focusEvent}/>
</div>
);
}
}
複製代碼
class ParentComp extends React.Component {
constructor(props) {
super(props);
// 建立ref 指向 ChildrenComp 組件實例
this.textInput = React.createRef();
}
componentDidMount() {
// 調用子組件 focusTextInput方法 觸發子組件內部 文本框獲取焦點事件
this.textInput.current.focusTextInput();
}
render() {
return (
<ChildrenComp ref={ this.textInput } /> ); } } 複製代碼
class ChildrenComp extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
focusTextInput() {
this.inputRef.current.focus();
}
render(){
return(
<div> <input type='text' value='父組件經過focusTextInput()方法控制獲取焦點事件' ref={ this.inputRef }/> </div> ) } } 複製代碼
React 也支持另一種使用 refs的方式成爲 「回調 refs」,能夠幫助咱們更精準的控制什麼時候 refs被設置和解除。 這個回調函數中接受 React 組件實例或 HTML DOM 元素做爲參數,以使它們能在其餘地方被存儲和訪問。
class TestComp extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 使用'ref'的回調函數將 text輸入框DOM節點的引用綁定到 React實例 this.textInput上
this.inputRef = element => {
this.textInput = element;
}
this.focus = () => {
if (this.textInput) {
this.textInput.focus();
}
}
}
componentDidMount() {
this.focus();
}
render() {
return (
<div> <input type='text' ref={ this.inputRef } /> </div> ); } } 複製代碼
React 將在組件掛載時會調用 ref 回調函數並傳入DOM 元素,當卸載時調用它並傳入 null。 在 componentDidMount 或 componentDidUpdate 觸發前,React 會保證 refs 必定是最新的。 在類組件中,一般父組件 把它的refs回調函數 經過props的形式傳遞給子組件,同時子組件把相同的函數做爲特殊的 ref屬性 傳遞給對應的 DOM 元素。
若是 ref 回調函數是之內聯函數的方式定義的,在更新過程當中它會被執行兩次,第一次傳入參數 null,而後第二次會傳入參數 DOM 元素。這是由於在每次渲染時會建立一個新的函數實例,因此 React 清空舊的 ref 而且設置新的。經過將 ref 的回調函數定義成 class 的綁定函數的方式能夠避免上述問題,可是大多數狀況下它是可有可無的。
class TestComp extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 初始化 flag 值爲 init
this.state = {
flag: 'init'
}
this.focus = () => {
if (this.textInput) {
this.textInput.focus();
}
}
}
componentDidMount() {
this.focus();
// 當執行完 render 首次渲染以後,更新狀態 flag 值 爲 update
this.setState({
flag: 'update'
});
}
render() {
return (
<div> {/* 經過內聯回調形式定義 ref */} <input type='text' value={this.state.flag} ref={(element) => { console.log('element', element); // 將傳入的 element 輸出控制檯 this.textInput = element; }} /> </div> ) } } 複製代碼
若是你目前還在使用 this.refs.textInput 這種方式訪問refs,官方建議使用 回調函數 或者 createRef API的方式來替換。
在極少數狀況下,咱們可能但願在父組件中引用子節點的 DOM 節點(官方不建議這樣操做,由於它會打破組件的封裝),用戶觸發焦點或者測量子DOM 節點的大小或者位置。雖然咱們能夠經過向子組件添加 ref的方式來解決,但這並非一個理想的解決方案,由於咱們只能獲取組件實例而不是 DOM節點。而且它還在函數組件上無效。
在react 16.3 或者更高版本中,咱們推薦使用 ref 轉發的方式來實現以上操做。
ref 轉發使得組件能夠像暴露本身的 ref同樣暴露子組件的 ref。
Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries.
Ref forwarding 是一種自動將ref 經過組件傳遞給其子節點的技術。下面咱們經過具體的案例來演示一下效果。
const ref = React.createRef();
const BtnComp = React.forwardRef((props, ref) => {
return (
<div> <button ref={ref} className='btn'> { props.children } </button> </div>
)
});
class TestComp extends React.Component {
clickEvent() {
if (ref && ref.current) {
ref.current.addEventListener('click', () => {
console.log('hello click!')
});
}
}
componentDidMount() {
console.log('當前按鈕的class爲:', ref.current.className); // btn
this.clickEvent(); // hello click!
}
render() {
return (
<div> <BtnComp ref={ref}>點擊我</BtnComp> </div>
);
}
}
複製代碼
上述案例,使用的組件BtnComp 能夠獲取對底層 button DOM 節點的引用並在必要時對其進行操做,就像正常的HTML元素 button直接使用DOM同樣。
第二個ref參數僅在使用React.forwardRef 回調 定義組件時存在。常規函數或類組件不接收ref參數,而且在props中也不提供ref。
Ref轉發不只限於DOM組件。您也能夠將refs轉發給類組件實例。
高階組件(HOC)是React中用於重用組件邏輯的高級技術,高階組件是一個獲取組件並返回新組件的函數。下面咱們經過具體的案例來看一下refs如何在高階組件鐘正常使用。
// 記錄狀態值變動操做
function logProps(Comp) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Comp ref={ forwardedRef } {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <LogProps { ...props } forwardedRef={ ref } />;
});
}
// 子組件
const BtnComp = React.forwardRef((props, ref) => {
return (
<div>
<button ref={ref} className='btn'>
{ props.children }
</button>
</div>
)
});
// 被logProps包裝後返回的新子組件
const NewBtnComp = logProps(BtnComp);
class TestComp extends React.Component {
constructor(props) {
super(props);
this.btnRef = React.createRef();
this.state = {
value: '初始化'
}
}
componentDidMount() {
console.log('ref', this.btnRef);
console.log('ref', this.btnRef.current.className);
this.btnRef.current.classList.add('cancel'); // 給BtnComp中的button添加一個class
this.btnRef.current.focus(); // focus到button元素上
setTimeout(() => {
this.setState({
value: '更新'
});
}, 10000);
}
render() {
return (
<NewBtnComp ref={this.btnRef}>{this.state.value}</NewBtnComp>
);
}
}
複製代碼
最終的效果圖以下:
註明:文章來源於公衆號 react_native, 已通過做者受權轉載。