對於 Ref 理解與使用,一些讀者可能還停留在用 ref 獲取真實 DOM 元素和獲取類元件範例層面上
其實 ref 除了這兩項常用功能之外,還有很多別的小技巧
通過本篇文章的學習,你將收穫 React ref 的基本和進階用法,並且能夠明白 React 內部是如何處理 ref 的,並通過一個小 Demo + 提問的方式帶你更加深刻地理解 ref 的底層原理
對於 Ref 的理解,要從兩個角度去分析:
Ref 物件的建立:使用 createRef
或 useRef
建立 Ref 物件 【相關推薦:Redis視訊教學、】
React 本身對 Ref 的處理:對於標籤中的 ref 屬性,React 是如何處理的
在類元件中,我們會通過 createRef
去建立一個 Ref 物件,其會被儲存在類元件範例上,它的實現很簡單
packages/react/src/ReactCreateRef.js
export function createRef(): RefObject {
const refObject = {
current: null,
}
return refObject
}
登入後複製
可以看到,就是建立了一個包含 current
屬性的物件,僅此而已
這也就意味著我們不能在函陣列件中使用 createRef
,因為每次函陣列件渲染都是一次新的函數執行,每次執行 createRef
得到的都是一個新的物件,無法保留其原來的參照
所以在函陣列件中,我們會使用 useRef
建立 Ref 物件,React 會將 useRef 和函陣列件對應的 fiber 物件關聯,將 useRef
建立的 ref 物件掛載到對應的 fiber 物件上
這樣一來每次函陣列件執行,只要函陣列件不被銷燬,那麼對應的 fiber 物件範例也會一直存在,所以 ref 也能夠被保留下來
首先要明確一個結論,在 React 中獲取 DOM 元素或者元件範例並不是只能通過 ref 物件獲取!!!
也就是說並不是只能通過先呼叫 createRef
建立 ref 物件,然後將它賦值到要獲取的元素或元件範例的 ref 屬性上,實際上還有別的方式
:::tip
只有類元件才有獲取元件範例這一說法,函陣列件沒有範例,不能被 ref 標記,但是可以通過 forwardRef
結合 useImperativeHandle
給函陣列件賦予 ref 標記的
:::
當我們給元素或類元件標籤中的 ref 屬性傳遞字串時,能夠在元件範例的 this.refs
中存取到
class Child extends React.Component<PropsWithChildren> {
render(): React.ReactNode {
const { children } = this.props
return (
<div>
<p>Child</p>
{children}
</div>
)
}
}
/** @description ref 屬性傳遞字串 */
class RefDemo1 extends React.Component {
logger = createLoggerWithScope('RefDemo1')
componentDidMount(): void {
this.logger.log(this.refs)
}
render(): React.ReactNode {
return (
<>
<div ref="refDemo1DOM">ref 屬性傳遞字串獲取 DOM 元素</div>
<Child ref="refDemo1Component">ref 屬性傳遞字串獲取類元件範例</Child>
</>
)
}
}
登入後複製
:::warning
這種方式已經被 React 官方廢棄,儘量不要使用
:::
ref 屬性傳遞函數時,會在 commit 階段建立真實 DOM 時執行 ref 指定的函數,並將元素作為第一個引數傳入,此時我們就可以利用它進行賦值以獲取 DOM 元素或元件範例
/** @description ref 屬性傳遞函數 */
class RefDemo2 extends React.Component {
logger = createLoggerWithScope('RefDemo2')
refDemo2DOM: HTMLElement | null = null
refDemo2Component: Child | null = null
componentDidMount(): void {
this.logger.log(this.refDemo2DOM)
this.logger.log(this.refDemo2Component)
}
render(): React.ReactNode {
return (
<>
<div ref={(el) => (this.refDemo2DOM = el)}>
ref 屬性傳遞函數獲取 DOM 元素
</div>
<Child ref={(child) => (this.refDemo2Component = child)}>
ref 屬性傳遞函數獲取類元件範例
</Child>
</>
)
}
}
登入後複製
這種方式就是我們最常用的方式了,使用 createRef
或者 useRef
建立 Ref 物件,並將其傳給標籤的 ref 屬性即可
這種方式獲取到的 ref 需要先呼叫 current
屬性才能獲取到對應的 DOM 元素或元件範例
/** @description ref 屬性傳遞物件 */
class RefDemo3 extends React.Component {
logger = createLoggerWithScope('RefDemo3')
refDemo3DOM = React.createRef<HTMLDivElement>()
refDemo3Component = React.createRef<Child>()
componentDidMount(): void {
this.logger.log(this.refDemo3DOM)
this.logger.log(this.refDemo3Component)
}
render(): React.ReactNode {
return (
<>
<div ref={this.refDemo3DOM}>ref 屬性傳遞物件獲取 DOM 元素</div>
<Child ref={this.refDemo3Component}>
ref 屬性傳遞物件獲取類元件範例
</Child>
</>
)
}
}
登入後複製
想要在爺元件中通過在子元件中傳遞 ref 獲取到孫元件的某個元素,也就是在爺元件中獲取到了孫元件的元素,是一種跨層級獲取
/** @description 孫元件 */
const Child: React.FC<{ grandRef: LegacyRef<HTMLDivElement> }> = (props) => {
const { grandRef } = props
return (
<>
<p>Child</p>
<div ref={grandRef}>要獲取的目標元素</div>
</>
)
}
/**
* @description 父元件
*
* 第一個泛型引數是 ref 的型別
* 第二個泛型引數是 props 的型別
*/
const Father = forwardRef<HTMLDivElement, {}>((props, ref) => {
return (
<div>
<Child grandRef={ref} />
</div>
)
})
/** @description 爺元件 */
const GrandFather: React.FC = () => {
let grandChildDiv: HTMLDivElement | null = null
useEffect(() => {
logger.log(grandChildDiv)
}, [])
return (
<div>
<Father ref={(el) => (grandChildDiv = el)} />
</div>
)
}
登入後複製
forwardRef 不僅可以轉發 ref 獲取 DOM 元素和元件範例,還可以轉發合併後的自定義 ref
什麼是「合併後的自定義 ref」呢?通過一個場景來看看就明白了
:::info{title=場景}
通過給 Foo 元件繫結 ref,獲取多個內容,包括:
子元件 Bar 的元件範例
Bar 元件中的 DOM 元素 button
孫元件 Baz 的元件範例
:::
這種在一個 ref 裡能夠存取多個元素和範例的就是「合併後的自定義 ref」
/** @description 自定義 ref 的型別 */
interface CustomRef {
bar: Bar
barButton: HTMLButtonElement
baz: Baz
}
class Baz extends React.Component {
render(): React.ReactNode {
return <div>Baz</div>
}
}
class Bar extends React.Component<{
customRef: ForwardedRef<CustomRef>
}> {
buttonEl: HTMLButtonElement | null = null
bazInstance: Baz | null = null
componentDidMount(): void {
const { customRef } = this.props
if (customRef) {
;(customRef as MutableRefObject<CustomRef>).current = {
bar: this,
barButton: this.buttonEl!,
baz: this.bazInstance!,
}
}
}
render() {
return (
<>
<button ref={(el) => (this.buttonEl = el)}>Bar button</button>
<Baz ref={(instance) => (this.bazInstance = instance)} />
</>
)
}
}
const FowardRefBar = forwardRef<CustomRef>((props, ref) => (
<Bar {...props} customRef={ref} />
))
const Foo: React.FC = () => {
const customRef = useRef<CustomRef>(null)
useEffect(() => {
logger.log(customRef.current)
}, [])
return <FowardRefBar ref={customRef} />
}
登入後複製
如果我們在高階元件中直接使用 ref,它會直接指向 WrapComponent
class TestComponent extends React.Component {
render(): React.ReactNode {
return <p>TestComponent</p>
}
}
/** @description 不使用 forwardRef 轉發 HOC 中的 ref */
const HOCWithoutForwardRef = (Component: typeof React.Component) => {
class WrapComponent extends React.Component {
render(): React.ReactNode {
return (
<div>
<p>WrapComponent</p>
<Component />
</div>
)
}
}
return WrapComponent
}
const HOCComponent1 = HOCWithoutForwardRef(TestComponent)
const RefHOCWithoutForwardRefDemo = () => {
const logger = createLoggerWithScope('RefHOCWithoutForwardRefDemo')
const wrapRef = useRef(null)
useEffect(() => {
// wrapRef 指向的是 WrapComponent 範例 而不是 HOCComponent1 範例
logger.log(wrapRef.current)
}, [])
return <HOCComponent1 ref={wrapRef} />
}
登入後複製
如果我們希望 ref
指向的是被包裹的 TestComponent 而不是 HOC 內部的 WrapComponent 時該怎麼辦呢?
這時候就可以用 forwardRef 進行轉發了
/** @description HOC 中使用 forwardRef 轉發 ref */
const HOCWithForwardRef = (Component: typeof React.Component) => {
class WrapComponent extends React.Component<{
forwardedRef: LegacyRef<any>
}> {
render(): React.ReactNode {
const { forwardedRef } = this.props
return (
<div>
<p>WrapComponent</p>
<Component ref={forwardedRef} />
</div>
)
}
}
return React.forwardRef((props, ref) => (
<WrapComponent forwardedRef={ref} {...props} />
))
}
const HOCComponent2 = HOCWithForwardRef(TestComponent)
const RefHOCWithForwardRefDemo = () => {
const logger = createLoggerWithScope('RefHOCWithForwardRefDemo')
const hocComponent2Ref = useRef(null)
useEffect(() => {
// hocComponent2Ref 指向的是 HOCComponent2 範例
logger.log(hocComponent2Ref.current)
}, [])
return <HOCComponent2 ref={hocComponent2Ref} />
}
登入後複製
一般我們可以通過父元件改變子元件 props 的方式觸發子元件的更新渲染完成元件間通訊
但如果我們不希望通過這種改變子元件 props 的方式的話還能有別的辦法嗎?
可以通過 ref 獲取子元件範例,然後子元件暴露出通訊的方法,父元件呼叫該方法即可觸發子元件的更新渲染
對於函陣列件,由於其不存在元件範例這樣的說法,但我們可以通過 useImperativeHandle
這個 hook 來指定 ref 參照時得到的屬性和方法,下面我們分別用類元件和函陣列件都實現一遍
/**
* 父 -> 子 使用 ref
* 子 -> 父 使用 props 回撥
*/
class CommunicationDemoFather extends React.Component<
{},
CommunicationDemoFatherState
> {
state: Readonly<CommunicationDemoFatherState> = {
fatherToChildMessage: '',
childToFatherMessage: '',
}
childRef = React.createRef<CommunicationDemoChild>()
/** @description 提供給子元件修改父元件中的狀態 */
handleChildToFather = (message: string) => {
this.setState((state) => ({
...state,
childToFatherMessage: message,
}))
}
constructor(props: {}) {
super(props)
this.handleChildToFather = this.handleChildToFather.bind(this)
}
render(): React.ReactNode {
const { fatherToChildMessage, childToFatherMessage } = this.state
return (
<div className={s.father}>
<h3>父元件</h3>
<p>子元件對我說:{childToFatherMessage}</p>
<div className={s.messageInputBox}>
<section>
<label htmlFor="to-father">我對子元件說:</label>
<input
type="text"
id="to-child"
onChange={(e) =>
this.setState((state) => ({
...state,
fatherToChildMessage: e.target.value,
}))
}
/>
</section>
{/* 父 -> 子 -- 使用 ref 完成元件通訊 */}
<button
onClick={() =>
this.childRef.current?.setFatherToChildMessage(
fatherToChildMessage,
)
}
>
傳送
</button>
</div>
<CommunicationDemoChild
ref={this.childRef}
onChildToFather={this.handleChildToFather}
/>
</div>
)
}
}
interface CommunicationDemoChildProps {
onChildToFather: (message: string) => void
}
// 子元件自己維護狀態 不依賴於父元件 props
interface CommunicationDemoChildState {
fatherToChildMessage: string
childToFatherMessage: string
}
class CommunicationDemoChild extends React.Component<
CommunicationDemoChildProps,
CommunicationDemoChildState
> {
state: Readonly<CommunicationDemoChildState> = {
fatherToChildMessage: '',
childToFatherMessage: '',
}
/** @description 暴露給父元件使用的 API -- 修改父到子的訊息 fatherToChildMessage */
setFatherToChildMessage(message: string) {
this.setState((state) => ({ ...state, fatherToChildMessage: message }))
}
render(): React.ReactNode {
const { onChildToFather: emitChildToFather } = this.props
const { fatherToChildMessage, childToFatherMessage } = this.state
return (
<div className={s.child}>
<h3>子元件</h3>
<p>父元件對我說:{fatherToChildMessage}</p>
<div className={s.messageInputBox}>
<section>
<label htmlFor="to-father">我對父元件說:</label>
<input
type="text"
id="to-father"
onChange={(e) =>
this.setState((state) => ({
...state,
childToFatherMessage: e.target.value,
}))
}
/>
</section>
{/* 子 -> 父 -- 使用 props 回撥完成元件通訊 */}
<button onClick={() => emitChildToFather(childToFatherMessage)}>
傳送
</button>
</div>
</div>
)
}
}
登入後複製
使用 useImperativeHandle
hook 可以讓我們指定 ref 參照時能獲取到的屬性和方法,個人認為相比類元件的 ref,使用這種方式能夠更加好的控制元件想暴露給外界的 API
而不像類元件那樣直接全部暴露出去,當然,如果你想在類元件中只暴露部分 API 的話,可以用前面說的合併轉發自定義 ref 的方式去完成
接下來我們就用 useImperativeHandle
hook 改造上面的類元件實現的 demo 吧
interface ChildRef {
setFatherToChildMessage: (message: string) => void
}
/**
* 父 -> 子 使用 ref
* 子 -> 父 使用 props 回撥
*/
const CommunicationDemoFunctionComponentFather: React.FC = () => {
const [fatherToChildMessage, setFatherToChildMessage] = useState('')
const [childToFatherMessage, setChildToFatherMessage] = useState('')
const childRef = useRef<ChildRef>(null)
return (
<div className={s.father}>
<h3>父元件</h3>
<p>子元件對我說:{childToFatherMessage}</p>
<div className={s.messageInputBox}>
<section>
<label htmlFor="to-father">我對子元件說:</label>
<input
type="text"
id="to-child"
onChange={(e) => setFatherToChildMessage(e.target.value)}
/>
</section>
{/* 父 -> 子 -- 使用 ref 完成元件通訊 */}
<button
onClick={() =>
childRef.current?.setFatherToChildMessage(fatherToChildMessage)
}
>
傳送
</button>
</div>
<CommunicationDemoFunctionComponentChild
ref={childRef}
onChildToFather={(message) => setChildToFatherMessage(message)}
/>
</div>
)
}
interface CommunicationDemoFunctionComponentChildProps {
onChildToFather: (message: string) => void
}
const CommunicationDemoFunctionComponentChild = forwardRef<
ChildRef,
CommunicationDemoFunctionComponentChildProps
>((props, ref) => {
const { onChildToFather: emitChildToFather } = props
// 子元件自己維護狀態 不依賴於父元件 props
const [fatherToChildMessage, setFatherToChildMessage] = useState('')
const [childToFatherMessage, setChildToFatherMessage] = useState('')
// 定義暴露給外界的 API
useImperativeHandle(ref, () => ({ setFatherToChildMessage }))
return (
<div className={s.child}>
<h3>子元件</h3>
<p>父元件對我說:{fatherToChildMessage}</p>
<div className={s.messageInputBox}>
<section>
<label htmlFor="to-father">我對父元件說:</label>
<input
type="text"
id="to-father"
onChange={(e) => setChildToFatherMessage(e.target.value)}
/>
</section>
{/* 子 -> 父 -- 使用 props 回撥完成元件通訊 */}
<button onClick={() => emitChildToFather(childToFatherMessage)}>
傳送
</button>
</div>
</div>
)
})
登入後複製
當我們在函陣列件中如果資料更新後不希望檢視改變,也就是說檢視不依賴於這個資料,這個時候可以考慮用 useRef
對這種資料進行快取
為什麼 useRef
可以對資料進行快取?
還記得之前說的 useRef 在函陣列件中的作用原理嗎?
React 會將 useRef 和函陣列件對應的 fiber 物件關聯,將 useRef 建立的 ref 物件掛載到對應的 fiber 物件上,這樣一來每次函陣列件執行,只要函陣列件不被銷燬,那麼對應的 fiber 物件範例也會一直存在,所以 ref 也能夠被保留下來
利用這個特性,我們可以將資料放到 useRef
中,由於它在記憶體中一直都是同一塊記憶體地址,所以無論如何變化都不會影響到檢視的改變
:::warning{title=注意}
一定要看清前提,只適用於與檢視無關的資料
:::
我們通過一個簡單的 demo 來更清楚地體會下這個應用場景
假設我有一個 todoList
列表,檢視上會把這個列表渲染出來,並且有一個資料 activeTodoItem 是控制當前選中的是哪個 todoItem
點選 todoItem 會切換這個 activeTodoItem,但是並不需要在檢視上作出任何變化,如果使用 useState
去儲存 activeTodoItem,那麼當其變化時會導致函陣列件重新執行,檢視重新渲染,但在這個場景中我們並不希望更新檢視
相對的,我們希望這個 activeTodoItem 資料被快取起來,不會隨著檢視的重新渲染而導致其作為 useState
的執行結果重新生成一遍,因此我們可以改成用 useRef
實現,因為其在記憶體中一直都是同一塊記憶體地址,這樣就不會因為它的改變而更新檢視了
同理,在 useEffect 中如果使用到了 useRef 的資料,也不需要將其宣告到 deps 陣列中,因為其記憶體地址不會變化,所以每次在 useEffect 中獲取到的 ref 資料一定是最新的
interface TodoItem {
id: number
name: string
}
const todoList: TodoItem[] = [
{
id: 1,
name: 'coding',
},
{
id: 2,
name: 'eating',
},
{
id: 3,
name: 'sleeping',
},
{
id: 4,
name: 'playing',
},
]
const CacheDataWithRefDemo: React.FC = () => {
const activeTodoItem = useRef(todoList[0])
// 模擬 componentDidUpdate -- 如果改變 activeTodoItem 後元件沒重新渲染,說明檢視可以不依賴於 activeTodoItem 資料
useEffect(() => {
logger.log('檢測元件是否有更新')
})
return (
<div className={s.container}>
<div className={s.list}>
{todoList.map((todoItem) => (
<div
key={todoItem.id}
className={s.item}
onClick={() => (activeTodoItem.current = todoItem)}
>
<p>{todoItem.name}</p>
</div>
))}
</div>
<button onClick={() => logger.log(activeTodoItem.current)}>
控制檯輸出最新的 activeTodoItem
</button>
</div>
)
}
登入後複製
首先先看一個關於 callback ref 的小 Demo 來引出我們後續的內容
interface RefDemo8State {
counter: number
}
class RefDemo8 extends React.Component<{}, RefDemo8State> {
state: Readonly<RefDemo8State> = {
counter: 0,
}
el: HTMLDivElement | null = null
render(): React.ReactNode {
return (
<div>
<div
ref={(el) => {
this.el = el
console.log('this.el -- ', this.el)
}}
>
ref element
</div>
<button
onClick={() => this.setState({ counter: this.state.counter + 1 })}
>
add
</button>
</div>
)
}
}
登入後複製
為什麼會執行兩次?為什麼第一次 this.el === null
?為什麼第二次又正常了?
還記得 React 底層是有 render 階段和 commit 階段的嗎?關於 ref 的處理邏輯就在 commit 階段進行的
React 底層有兩個關於 ref 的處理常式 -- commitDetachRef
和 commitAttachRef
上面的 Demo 中 callback ref 執行了兩次正是對應著這兩次函數的呼叫,大致來講可以理解為 commitDetachRef
在 DOM 更新之前執行,commitAttachRef
在 DOM 更新之後執行
這也就不難理解為什麼會有上面 Demo 中的現象了,但我們還是要結合原始碼來看看,加深自己的理解
在新版本的 React 原始碼中它改名為了 safelyDetachRef,但是核心邏輯沒變,這裡我將核心邏輯簡化出來供大家閱讀:
packages/react-reconciler/src/ReactFiberCommitWork.js
function commitDetachRef(current: Fiber) {
// current 是已經調和完了的 fiber 物件
const currentRef = current.ref
if (currentRef !== null) {
if (typeof currentRef === 'function') {
// callback ref 和 string ref 執行時機
currentRef(null)
} else {
// object ref 處理時機
currentRef.current = null
}
}
}
登入後複製
可以看到,就是從 fiber 中取出 ref,然後根據 callback ref、string ref、object ref 的情況進行處理
並且也能看到 commitDetachRef
主要是將 ref 置為 null,這也就是為什麼 RefDemo8
中第一次執行的 callback ref 中看到的 this.el 是 null 了
核心邏輯程式碼如下:
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref
if (ref !== null) {
const instance = finishedWork.stateNode
let instanceToUse
// 處理 ref 來源
switch (finishedWork.tag) {
// HostComponent 代表 DOM 元素型別的 tag
case HostComponent:
instanceToUse = getPublicInstance(instance)
break
// 類元件使用元件範例
default:
instanceToUse = instance
}
if (typeof ref === 'function') {
// callback ref 和 string ref
ref(instanceToUse)
} else {
// object ref
ref.current = instanceToUse
}
}
}
登入後複製
從上面的核心原始碼中能看到,對於 callback ref
和 string ref
,都是統一以函數的方式呼叫,將 null
或 instanceToUse
傳入
callback ref
這樣做還能理解,但是為什麼 string ref
也是這樣處理呢?
因為當 React 檢測到是 string ref
時,會自動繫結一個函數用於處理 string ref
,核心原始碼邏輯如下:
packages/react-reconciler/src/ReactChildFiber.js
// 從元素上獲取 ref
const mixedRef = element.ref
const stringRef = '' + mixedRef
const ref = function (value) {
// resolvedInst 就是元件範例
const refs = resolvedInst.refs
if (value === null) {
delete refs[stringRef]
} else {
refs[stringRef] = value
}
}
登入後複製
這樣一來 string ref 也變成了一個函數了,從而可以在 commitDetachRef
和 commitAttachRef
中被執行,並且也能印證為什麼 string ref
會在類元件範例的 refs
屬性中獲取到
為什麼在 RefDemo8 中我們每次點選按鈕時都會觸發 commitDetachRef
和 commitAttachRef
呢?這就需要聊聊 ref 的執行時機了,而從上文也能夠了解到,ref 底層實際上是由 commitDetachRef
和 commitAttachRef
在處理核心邏輯
那麼我們就得來看看這兩個函數的執行時機才能行
packages/react-reconciler/src/ReactFiberCommitWork.js
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const current = finishedWork.alternate
const flags = finishedWork.flags
if (flags & Ref) {
if (current !== null) {
// 也就是 commitDetachRef
safelyDetachRef(current, current.return)
}
}
}
登入後複製
packages/react-reconciler/src/ReactFiberCommitWork.js
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
) {
const flags = finishedWork.flags
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return)
}
}
登入後複製
可以看到,只有當 fiber 被打上了 Ref
這個 flag tag 時才會去執行 commitDetachRef
/commitAttachRef
那麼什麼時候會標記 Ref tag 呢?
packages/react-reconciler/src/ReactFiberBeginWork.js
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref
if (
// current === null 意味著是初次掛載,fiber 首次調和時會打上 Ref tag
(current === null && ref !== null) ||
// current !== null 意味著是更新,此時需要 ref 發生了變化才會打上 Ref tag
(current !== null && current.ref !== ref)
) {
// Schedule a Ref effect
workInProgress.flags |= Ref
}
}
登入後複製
那麼現在再回過頭來思考 RefDemo8
中為什麼每次點選按鈕都會執行 commitDetachRef
和 commitAttachRef
呢?
注意我們使用 callback ref
的時候是如何使用的
<div
ref={(el) => {
this.el = el
console.log('this.el -- ', this.el)
}}
>
ref element
</div>
登入後複製
是直接宣告了一個箭頭函數,這樣的方式會導致每次渲染這個 div 元素時,給 ref 賦值的都是一個新的箭頭函數,儘管函數的內容是一樣的,但記憶體地址不同,因而 current.ref !== ref
這個判斷條件會成立,從而每次都會觸發更新
那麼要如何解決這個問題呢?既然我們已經知道了問題的原因,那麼就好說了,只要讓每次賦值給 ref 的函數都是同一個就可以了唄~
const logger = createLoggerWithScope('RefDemo9')
interface RefDemo9Props {}
interface RefDemo9State {
counter: number
}
class RefDemo9 extends React.Component<RefDemo9Props, RefDemo9State> {
state: Readonly<RefDemo9State> = {
counter: 0,
}
el: HTMLDivElement | null = null
constructor(props: RefDemo9Props) {
super(props)
this.setElRef = this.setElRef.bind(this)
}
setElRef(el: HTMLDivElement | null) {
this.el = el
logger.log('this.el -- ', this.el)
}
render(): React.ReactNode {
return (
<div>
<div ref={this.setElRef}>ref element</div>
<button
onClick={() => this.setState({ counter: this.state.counter + 1 })}
>
add
</button>
</div>
)
}
}
登入後複製
這樣就完美解決啦,既修復了 bug,又搞懂了 ref 的底層原理,一舉兩得!
本篇文章我們學習到了:
【推薦學習:】
以上就是深入詳解React中的ref的詳細內容,更多請關注TW511.COM其它相關文章!