本篇從 React Refs 的使用場景、使用方式、注意事項,到 createRef
與 Hook useRef
的對比使用,最後以 React createRef
源碼結束,剖析整個 React Refs,關於 React.forwardRef
會在下一篇文章深刻探討。前端
React 的核心思想是每次對於界面 state 的改動,都會從新渲染整個Virtual DOM,而後新老的兩個 Virtual DOM 樹進行 diff(協調算法),對比出變化的地方,而後經過 render 渲染到實際的UI界面,node
使用 Refs 爲咱們提供了一種繞過狀態更新和從新渲染時訪問元素的方法;這在某些用例中頗有用,但不該該做爲 props
和 state
的替代方法。react
在項目開發中,若是咱們可使用 聲明式 或 提高 state 所在的組件層級(狀態提高) 的方法來更新組件,最好不要使用 refs。git
管理焦點(如文本選擇)或處理表單數據: Refs 將管理文本框當前焦點選中,或文本框其它屬性。github
在大多數狀況下,咱們推薦使用受控組件來處理表單數據。在一個受控組件中,表單數據是由 React 組件來管理的,每一個狀態更新都編寫數據處理函數。另外一種替代方案是使用非受控組件,這時表單數據將交由 DOM 節點來處理。要編寫一個非受控組件,就須要使用 Refs 來從 DOM 節點中獲取表單數據。算法
class NameForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit = (e) => {
console.log('A name was submitted: ' + this.input.current.value);
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
複製代碼
由於非受控組件將真實數據儲存在 DOM 節點中,因此再使用非受控組件時,有時候反而更容易同時集成 React 和非 React 代碼。若是你不介意代碼美觀性,而且但願快速編寫代碼,使用非受控組件每每能夠減小你的代碼量。不然,你應該使用受控組件。數組
媒體播放: 基於 React 的音樂或視頻播放器能夠利用 Refs 來管理其當前狀態(播放/暫停),或管理播放進度等。這些更新不須要進行狀態管理。安全
觸發強制動畫: 若是要在元素上觸發過強制動畫時,可使用 Refs 來執行此操做。函數
集成第三方 DOM 庫動畫
Refs 有 三種實現:
createRef
是 **React v16.3 ** 新增的API,容許咱們訪問 DOM 節點或在 render 方法中建立的 React 元素。
Refs 是使用 React.createRef()
建立的,並經過 ref
屬性附加到 React 元素。
Refs 一般在 React 組件的構造函數中定義,或者做爲函數組件頂層的變量定義,而後附加到 render()
函數中的元素。
export default class Hello extends React.Component {
constructor(props) {
super(props);
// 建立 ref 存儲 textRef DOM 元素
this.textRef = React.createRef();
}
componentDidMount() {
// 注意:經過 "current" 取得 DOM 節點
// 直接使用原生 API 使 text 輸入框得到焦點
this.textRef.current.focus();
}
render() {
// 把 <input> ref 關聯到構造器裏建立的 textRef 上
return <input ref={this.textRef} /> } } 複製代碼
使用 React.createRef()
給組件建立了 Refs 對象。在上面的示例中,ref被命名 textRef
,而後將其附加到 <input>
DOM元素。
其中, textRef
的屬性 current
指的是當前附加到 ref 的元素,並普遍用於訪問和修改咱們的附加元素。事實上,若是咱們經過登陸 myRef
控制檯進一步擴展咱們的示例,咱們將看到該 current
屬性確實是惟一可用的屬性:
componentDidMount = () => {
// myRef 僅僅有一個 current 屬性
console.log(this.textRef);
// myRef.current
console.log(this.textRef.current);
// component 渲染完成後,使 text 輸入框得到焦點
this.textRef.current.focus();
}
複製代碼
在 componentDidMount
生命週期階段,myRef.current
將按預期分配給咱們的 <input>
元素; componentDidMount
一般是使用 refs 處理一些初始設置的安全位置。
咱們不能在 componentWillMount
中更新 Refs,由於此時,組件還沒渲染完成, Refs 還爲 null
。
不一樣於傳遞 createRef()
建立的 ref
屬性,你會傳遞一個函數。這個函數中接受 React 組件實例或 HTML DOM 元素做爲參數,以使它們能在其餘地方被存儲和訪問。
import React from 'react';
export default class Hello extends React.Component {
constructor(props) {
super(props);
this.textRef = null; // 建立 ref 爲 null
}
componentDidMount() {
// 注意:這裏沒有使用 "current"
// 直接使用原生 API 使 text 輸入框得到焦點
this.textRef.focus();
}
render() {
// 把 <input> ref 關聯到構造器裏建立的 textRef 上
return <input ref={node => this.textRef = node} /> } } 複製代碼
React 將在組件掛載時將 DOM 元素傳入ref
回調函數並調用,當卸載時傳入 null
並調用它。在 componentDidMount
或 componentDidUpdate
觸發前,React 會保證 refs 必定是最新的。
像上例, ref
回調函數是之內聯函數的方式定義的,在更新過程當中它會被執行兩次,第一次傳入參數 null
,而後第二次會傳入參數 DOM 元素。
這是由於在每次渲染時會建立一個新的函數實例,因此 React 清空舊的 ref 而且設置新的。咱們能夠經過將 ref 的回調函數定義成 class 的綁定函數的方式能夠避免上述問題,可是大多數狀況下它是可有可無的。
export default class Hello extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// 經過 this.refs 調用
// 直接使用原生 API 使 text 輸入框得到焦點
this.refs.textRef.focus();
}
render() {
// 把 <input> ref 關聯到構造器裏建立的 textRef 上
return <input ref='textRef' /> } } 複製代碼
儘管字符串 stringRef 使用更方便,可是它有一些缺點,所以嚴格模式使用 stringRef 會報警告。官方推薦採用回調 Refs。
ref
屬性被用於一個普通的 HTML 元素時,React.createRef()
將接收底層 DOM 元素做爲它的 current
屬性以建立 ref
,咱們能夠經過 Refs 訪問 DOM 元素屬性。ref
屬性被用於一個自定義 class 組件時,ref
對象將接收該組件已掛載的實例做爲它的 current
,與 ref
用於 HTML 元素不一樣的是,咱們可以經過 ref
訪問該組件的props,state,方法以及它的整個原型 。stringRef
將會廢棄(嚴格模式下使用會報警告),React.createRef()
API 是 React v16.3 引入的更新。useRef
返回一個可變的 ref 對象,其 .current
屬性被初始化爲傳入的值。返回的 ref 對象在組件的整個生命週期內保持不變。
function Hello() {
const textRef = useRef(null)
const onButtonClick = () => {
// 注意:經過 "current" 取得 DOM 節點
textRef.current.focus();
};
return (
<>
<input ref={textRef} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
複製代碼
####區別
useRef()
比 ref
屬性更有用。useRef()
Hook 不只能夠用於 DOM refs, useRef()
建立的 ref
對象是一個 current
屬性可變且能夠容納任意值的通用容器,相似於一個 class 的實例屬性。
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
複製代碼
這是由於它建立的是一個普通 Javascript 對象。而 useRef()
和自建一個 {current: ...}
對象的惟一區別是,useRef
會在每次渲染時返回同一個 ref 對象。
請記住,當 ref 對象內容發生變化時,useRef
並不會通知你。變動 .current
屬性不會引起組件從新渲染。若是想要在 React 綁定或解綁 DOM 節點的 ref 時運行某些代碼,則須要使用回調 ref 來實現。
// ReactCreateRef.js 文件
import type {RefObject} from 'shared/ReactTypes';
// an immutable object with a single mutable value
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
// 封閉對象,阻止添加新屬性並將全部現有屬性標記爲不可配置。當前屬性的值只要可寫就能夠改變。
Object.seal(refObject);
}
return refObject;
}
複製代碼
其中 RefObject
爲:
export type RefObject = {|
current: any,
|};
複製代碼
這就是的 createRef 源碼,實現很簡單,但具體的它如何使用,如何掛載,將在後面的 React 渲染中介紹,敬請期待。
想看更過系列文章,點擊前往 github 博客主頁
走在最後,歡迎關注:前端瓶子君,每日更新