Regarding the understanding and use of Ref, some readers may still stay at the level of using ref to obtain real DOM elements and obtain class component instances
In fact, in addition to these two common uses of ref In addition to functions, there are many other tips
Through the study of this article, you will gain the basic and advanced usage of React ref, and be able to understand how React handles ref internally, and use a The way of asking questions in the small demo will give you a deeper understanding of the underlying principles of ref
1. The understanding and use of ref
The understanding of Ref must be analyzed from two perspectives:
Ref object creation: Use
createRef
oruseRef
to create a Ref object [Related recommendations: Redis video tutorial, Programming video】React's own handling of Ref: How does React handle the ref attribute in the tag
1.1. Creation of ref objects
1.1.1. createRef
In the class component, we will go through createRef
Create a Ref object, which will be saved on the class component instance. Its implementation is very simple
packages/react/src/ReactCreateRef.js
export function createRef(): RefObject { const refObject = { current: null, } return refObject }
You can see To, it is to create an object containing the current
attribute, nothing more
1.1.2. useRef
This also means that we createRef
cannot be used in function components, because each time the function component is rendered, it is a new function execution, and each time createRef
is executed, a new object is obtained and cannot be retained. Original reference
So in the function component, we will use useRef
to create the Ref object. React will associate useRef with the fiber object corresponding to the function component, and useRef
The created ref object is mounted to the corresponding fiber object
In this way, every time the function component is executed, as long as the function component is not destroyed, the corresponding fiber object instance will always exist, so ref can also be Keep it
1.2. React’s processing of ref attributes in tags
First of all, we must make a clear conclusion. Obtaining DOM elements or component instances in React is not the only way ref object obtained! ! !
That is to say, it is not only possible to create a ref object by calling createRef
first, and then assign it to the ref attribute of the element or component instance to be obtained. In fact, there are other Method
:::tip
Only class components can obtain component instances. Function components have no instances and cannot be marked by ref, but they can be combined with forwardRef
useImperativeHandle
Assign the ref tag to the function component
:::
1.2.1. string ref
When we give When the ref attribute in an element or class component tag passes a string, it can be accessed in this.refs
of the component instance
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> </> ) } }
1.2.2. callback ref
When the ref attribute passes a function, the function specified by ref will be executed when the real DOM is created in the commit phase, and the element will be passed in as the first parameter. At this time, we can use it for assignment to obtain the DOM element or component instance/** @description ref 屬性傳遞函數(shù) */ 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 屬性傳遞函數(shù)獲取 DOM 元素 </div> <Child ref={(child) => (this.refDemo2Component = child)}> ref 屬性傳遞函數(shù)獲取類組件實例 </Child> </> ) } }
1.2.3. object ref
This method is our most commonly used method, usecreateRef Or
useRef Create a Ref object and pass it to the ref attribute of the tag.
current attribute first. Get the corresponding DOM element or component instance
/** @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> </> ) } }
2. ref Advanced usage
2.1. forwardRef forward ref
2.1.1. Cross-level acquisition
If you want to get an element of the grandchild component by passing ref in the child component, that is, you can get it in the master component. The elements of the grandchild component are a cross-level acquisition/** @description 孫組件 */ const Child: React.FC<{ grandRef: LegacyRef<HTMLDivElement> }> = (props) => { const { grandRef } = props return ( <> <p>Child</p> <div ref={grandRef}>要獲取的目標(biāo)元素</div> </> ) } /** * @description 父組件 * * 第一個泛型參數(shù)是 ref 的類型 * 第二個泛型參數(shù)是 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> ) }
2.1.2. Merge and forward custom ref
forwardRef can not only forward ref to obtain DOM elements and component instances , you can also forward the merged custom refWhat is the "merged custom ref"? You will understand if you look at it through a scene:::info{title=scene}By binding ref to the Foo component, you can obtain multiple contents, including:子組件 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} /> }
2.1.3. 高階組件轉(zhuǎn)發(fā) ref
如果我們在高階組件中直接使用 ref,它會直接指向 WrapComponent
class TestComponent extends React.Component { render(): React.ReactNode { return <p>TestComponent</p> } } /** @description 不使用 forwardRef 轉(zhuǎn)發(fā) 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 內(nèi)部的 WrapComponent 時該怎么辦呢?
這時候就可以用 forwardRef 進(jìn)行轉(zhuǎn)發(fā)了
/** @description HOC 中使用 forwardRef 轉(zhuǎn)發(fā) 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} /> }
2.2. ref 實現(xiàn)組件通信
一般我們可以通過父組件改變子組件 props 的方式觸發(fā)子組件的更新渲染完成組件間通信
但如果我們不希望通過這種改變子組件 props 的方式的話還能有別的辦法嗎?
可以通過 ref 獲取子組件實例,然后子組件暴露出通信的方法,父組件調(diào)用該方法即可觸發(fā)子組件的更新渲染
對于函數(shù)組件,由于其不存在組件實例這樣的說法,但我們可以通過 useImperativeHandle
這個 hook 來指定 ref 引用時得到的屬性和方法,下面我們分別用類組件和函數(shù)組件都實現(xiàn)一遍
2.2.1. 類組件 ref 暴露組件實例
/** * 父 -> 子 使用 ref * 子 -> 父 使用 props 回調(diào) */ class CommunicationDemoFather extends React.Component< {}, CommunicationDemoFatherState > { state: Readonly<CommunicationDemoFatherState> = { fatherToChildMessage: '', childToFatherMessage: '', } childRef = React.createRef<CommunicationDemoChild>() /** @description 提供給子組件修改父組件中的狀態(tài) */ 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, ) } > 發(fā)送 </button> </div> <CommunicationDemoChild ref={this.childRef} onChildToFather={this.handleChildToFather} /> </div> ) } } interface CommunicationDemoChildProps { onChildToFather: (message: string) => void } // 子組件自己維護(hù)狀態(tài) 不依賴于父組件 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 回調(diào)完成組件通信 */} <button onClick={() => emitChildToFather(childToFatherMessage)}> 發(fā)送 </button> </div> </div> ) } }
2.2.2. 函數(shù)組件 ref 暴露指定方法
使用 useImperativeHandle
hook 可以讓我們指定 ref 引用時能獲取到的屬性和方法,個人認(rèn)為相比類組件的 ref,使用這種方式能夠更加好的控制組件想暴露給外界的 API
而不像類組件那樣直接全部暴露出去,當(dāng)然,如果你想在類組件中只暴露部分 API 的話,可以用前面說的合并轉(zhuǎn)發(fā)自定義 ref 的方式去完成
接下來我們就用 useImperativeHandle
hook 改造上面的類組件實現(xiàn)的 demo 吧
interface ChildRef { setFatherToChildMessage: (message: string) => void } /** * 父 -> 子 使用 ref * 子 -> 父 使用 props 回調(diào) */ 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) } > 發(fā)送 </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 // 子組件自己維護(hù)狀態(tài) 不依賴于父組件 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 回調(diào)完成組件通信 */} <button onClick={() => emitChildToFather(childToFatherMessage)}> 發(fā)送 </button> </div> </div> ) })
2.3. 函數(shù)組件緩存數(shù)據(jù)
當(dāng)我們在函數(shù)組件中如果數(shù)據(jù)更新后不希望視圖改變,也就是說視圖不依賴于這個數(shù)據(jù),這個時候可以考慮用 useRef
對這種數(shù)據(jù)進(jìn)行緩存
為什么 useRef
可以對數(shù)據(jù)進(jìn)行緩存?
還記得之前說的 useRef 在函數(shù)組件中的作用原理嗎?
React 會將 useRef 和函數(shù)組件對應(yīng)的 fiber 對象關(guān)聯(lián),將 useRef 創(chuàng)建的 ref 對象掛載到對應(yīng)的 fiber 對象上,這樣一來每次函數(shù)組件執(zhí)行,只要函數(shù)組件不被銷毀,那么對應(yīng)的 fiber 對象實例也會一直存在,所以 ref 也能夠被保留下來
利用這個特性,我們可以將數(shù)據(jù)放到 useRef
中,由于它在內(nèi)存中一直都是同一塊內(nèi)存地址,所以無論如何變化都不會影響到視圖的改變
:::warning{title=注意}
一定要看清前提,只適用于與視圖無關(guān)的數(shù)據(jù)
:::
我們通過一個簡單的 demo 來更清楚地體會下這個應(yīng)用場景
假設(shè)我有一個 todoList
列表,視圖上會把這個列表渲染出來,并且有一個數(shù)據(jù) activeTodoItem 是控制當(dāng)前選中的是哪個 todoItem
點擊 todoItem 會切換這個 activeTodoItem,但是并不需要在視圖上作出任何變化,如果使用 useState
去保存 activeTodoItem,那么當(dāng)其變化時會導(dǎo)致函數(shù)組件重新執(zhí)行,視圖重新渲染,但在這個場景中我們并不希望更新視圖
相對的,我們希望這個 activeTodoItem 數(shù)據(jù)被緩存起來,不會隨著視圖的重新渲染而導(dǎo)致其作為 useState
的執(zhí)行結(jié)果重新生成一遍,因此我們可以改成用 useRef
實現(xiàn),因為其在內(nèi)存中一直都是同一塊內(nèi)存地址,這樣就不會因為它的改變而更新視圖了
同理,在 useEffect 中如果使用到了 useRef 的數(shù)據(jù),也不需要將其聲明到 deps 數(shù)組中,因為其內(nèi)存地址不會變化,所以每次在 useEffect 中獲取到的 ref 數(shù)據(jù)一定是最新的
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 數(shù)據(jù) 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> ) }
3. 通過 callback ref 探究 ref 原理
首先先看一個關(guān)于 callback ref 的小 Demo 來引出我們后續(xù)的內(nèi)容
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> ) } }
為什么會執(zhí)行兩次?為什么第一次 this.el === null
?為什么第二次又正常了?
3.1. ref 的底層原理
還記得 React 底層是有 render 階段和 commit 階段的嗎?關(guān)于 ref 的處理邏輯就在 commit 階段進(jìn)行的
React 底層有兩個關(guān)于 ref 的處理函數(shù) -- commitDetachRef
和 commitAttachRef
上面的 Demo 中 callback ref 執(zhí)行了兩次正是對應(yīng)著這兩次函數(shù)的調(diào)用,大致來講可以理解為 commitDetachRef
在 DOM 更新之前執(zhí)行,commitAttachRef
在 DOM 更新之后執(zhí)行
這也就不難理解為什么會有上面 Demo 中的現(xiàn)象了,但我們還是要結(jié)合源碼來看看,加深自己的理解
3.1.1. commitDetachRef
在新版本的 React 源碼中它改名為了 safelyDetachRef,但是核心邏輯沒變,這里我將核心邏輯簡化出來供大家閱讀:
packages/react-reconciler/src/ReactFiberCommitWork.js
function commitDetachRef(current: Fiber) { // current 是已經(jīng)調(diào)和完了的 fiber 對象 const currentRef = current.ref if (currentRef !== null) { if (typeof currentRef === 'function') { // callback ref 和 string ref 執(zhí)行時機 currentRef(null) } else { // object ref 處理時機 currentRef.current = null } } }
可以看到,就是從 fiber 中取出 ref,然后根據(jù) callback ref、string ref、object ref 的情況進(jìn)行處理
并且也能看到 commitDetachRef
主要是將 ref 置為 null,這也就是為什么 RefDemo8
中第一次執(zhí)行的 callback ref 中看到的 this.el 是 null 了
3.1.2. commitAttachRef
核心邏輯代碼如下:
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 } } }
3.2. 為什么 string ref 也是以函數(shù)的方式調(diào)用?
從上面的核心源碼中能看到,對于 callback ref
和 string ref
,都是統(tǒng)一以函數(shù)的方式調(diào)用,將 null
或 instanceToUse
傳入
callback ref
這樣做還能理解,但是為什么 string ref
也是這樣處理呢?
因為當(dāng) React 檢測到是 string ref
時,會自動綁定一個函數(shù)用于處理 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 也變成了一個函數(shù)了,從而可以在 commitDetachRef
和 commitAttachRef
中被執(zhí)行,并且也能印證為什么 string ref
會在類組件實例的 refs
屬性中獲取到
3.3. ref 的執(zhí)行時機
為什么在 RefDemo8 中我們每次點擊按鈕時都會觸發(fā) commitDetachRef
和 commitAttachRef
呢?這就需要聊聊 ref 的執(zhí)行時機了,而從上文也能夠了解到,ref 底層實際上是由 commitDetachRef
和 commitAttachRef
在處理核心邏輯
那么我們就得來看看這兩個函數(shù)的執(zhí)行時機才能行
3.3.1. commitDetachRef 執(zhí)行時機
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) } } }
3.3.2. commitAttachRef 執(zhí)行時機
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) } }
3.3.3. fiber 何時打上 Ref tag?
可以看到,只有當(dāng) fiber 被打上了 Ref
這個 flag tag 時才會去執(zhí)行 commitDetachRef
/commitAttachRef
那么什么時候會標(biāo)記 Ref tag 呢?
packages/react-reconciler/src/ReactFiberBeginWork.js
function markRef(current: Fiber | null, workInProgress: Fiber) { const ref = workInProgress.ref if ( // current === null 意味著是初次掛載,fiber 首次調(diào)和時會打上 Ref tag (current === null && ref !== null) || // current !== null 意味著是更新,此時需要 ref 發(fā)生了變化才會打上 Ref tag (current !== null && current.ref !== ref) ) { // Schedule a Ref effect workInProgress.flags |= Ref } }
3.3.4. 為什么每次點擊按鈕 callback ref 都會執(zhí)行?
那么現(xiàn)在再回過頭來思考 RefDemo8
中為什么每次點擊按鈕都會執(zhí)行 commitDetachRef
和 commitAttachRef
呢?
注意我們使用 callback ref
的時候是如何使用的
<div ref={(el) => { this.el = el console.log('this.el -- ', this.el) }} > ref element </div>
是直接聲明了一個箭頭函數(shù),這樣的方式會導(dǎo)致每次渲染這個 div 元素時,給 ref 賦值的都是一個新的箭頭函數(shù),盡管函數(shù)的內(nèi)容是一樣的,但內(nèi)存地址不同,因而 current.ref !== ref
這個判斷條件會成立,從而每次都會觸發(fā)更新
3.3.5. 如何解決?
那么要如何解決這個問題呢?既然我們已經(jīng)知道了問題的原因,那么就好說了,只要讓每次賦值給 ref 的函數(shù)都是同一個就可以了唄~
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> ) } }
這樣就完美解決啦,既修復(fù)了 bug,又搞懂了 ref 的底層原理,一舉兩得!
4. 總結(jié)
本篇文章我們學(xué)習(xí)到了:
- ref 的理解與使用,包括如何創(chuàng)建 ref 對象,以及除了 object ref 之外的 string ref 和 callback ref 的方式去使用 ref
- ref 的高階用法,包括 forwardRef 轉(zhuǎn)發(fā) ref、ref 實現(xiàn)組件通信、利用 ref 在函數(shù)組件中緩存數(shù)據(jù)等
- 通過一個簡單的 callback ref 的 Demo 研究 ref 的底層原理,string ref 為何也是以函數(shù)的方式被調(diào)用,以及 ref 的執(zhí)行時機
【推薦學(xué)習(xí):javascript視頻教程】
The above is the detailed content of An in-depth explanation of ref in React. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Netflixusesacustomframeworkcalled"Gibbon"builtonReact,notReactorVuedirectly.1)TeamExperience:Choosebasedonfamiliarity.2)ProjectComplexity:Vueforsimplerprojects,Reactforcomplexones.3)CustomizationNeeds:Reactoffersmoreflexibility.4)Ecosystema

The React ecosystem includes state management libraries (such as Redux), routing libraries (such as ReactRouter), UI component libraries (such as Material-UI), testing tools (such as Jest), and building tools (such as Webpack). These tools work together to help developers develop and maintain applications efficiently, improve code quality and development efficiency.

Netflix uses React as its front-end framework. 1) React's componentized development model and strong ecosystem are the main reasons why Netflix chose it. 2) Through componentization, Netflix splits complex interfaces into manageable chunks such as video players, recommendation lists and user comments. 3) React's virtual DOM and component life cycle optimizes rendering efficiency and user interaction management.

React is a JavaScript library developed by Meta for building user interfaces, with its core being component development and virtual DOM technology. 1. Component and state management: React manages state through components (functions or classes) and Hooks (such as useState), improving code reusability and maintenance. 2. Virtual DOM and performance optimization: Through virtual DOM, React efficiently updates the real DOM to improve performance. 3. Life cycle and Hooks: Hooks (such as useEffect) allow function components to manage life cycles and perform side-effect operations. 4. Usage example: From basic HelloWorld components to advanced global state management (useContext and

React's future will focus on the ultimate in component development, performance optimization and deep integration with other technology stacks. 1) React will further simplify the creation and management of components and promote the ultimate in component development. 2) Performance optimization will become the focus, especially in large applications. 3) React will be deeply integrated with technologies such as GraphQL and TypeScript to improve the development experience.

The advantages of React are its flexibility and efficiency, which are reflected in: 1) Component-based design improves code reusability; 2) Virtual DOM technology optimizes performance, especially when handling large amounts of data updates; 3) The rich ecosystem provides a large number of third-party libraries and tools. By understanding how React works and uses examples, you can master its core concepts and best practices to build an efficient, maintainable user interface.

Netflix mainly uses React as the front-end framework, supplemented by Vue for specific functions. 1) React's componentization and virtual DOM improve the performance and development efficiency of Netflix applications. 2) Vue is used in Netflix's internal tools and small projects, and its flexibility and ease of use are key.

React is a front-end framework for building user interfaces; a back-end framework is used to build server-side applications. React provides componentized and efficient UI updates, and the backend framework provides a complete backend service solution. When choosing a technology stack, project requirements, team skills, and scalability should be considered.
