亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

React中的?ref?及原理解析

 更新時間:2025年01月01日 08:27:21   作者:袋鼠云數(shù)棧前端  
本章深入探討了React?Ref的用法和原理,還介紹了如何使用useImperativeHandle在函數(shù)組件中暴露方法,并詳細(xì)解釋了ref的處理邏輯和原理,包括在commit階段更新ref以及在組件卸載時的處理,感興趣的朋友一起看看吧

前言

對于 ref 的理解,我們一部人還停留在用 ref 獲取真實 dom 元素和獲取組件層面上,但實際 ref 除了這兩項功能之外,在使用上還有很多小技巧。本章我們就一起深入探討研究一下 React ref 的用法和原理;本章中所有的源碼節(jié)選來自 16.8 版本

基本概念和使用

此部分將分成兩個部分去分析,第一部分是 ref 對象的創(chuàng)建,第二部分是 React 本身對 ref 的處理;兩者不要混為一談,所謂 ref 對象的創(chuàng)建,就是通過 React.createRef 或者 React.useRef 來創(chuàng)建一個 ref 原始對象。而 React 對 ref 處理,主要指的是對于標(biāo)簽中 ref 屬性,React 是如何處理以及 React 轉(zhuǎn)發(fā) ref 。下面來仔細(xì)介紹一下。

ref 對象的創(chuàng)建

什么是 ref ?

所謂 ref 對象就是用 createRef 或者 useRef 創(chuàng)建出來的對象,一個標(biāo)準(zhǔn)的 ref 對象應(yīng)該是如下的樣子:

{
  current: null, // current指向ref對象獲取到的實際內(nèi)容,可以是dom元素,組件實例。
}

React 提供兩種方法創(chuàng)建 ref 對象

類組件 React.createRef

class Index extends React.Component{
    constructor(props){
       super(props)
       this.currentDom = React.createRef(null)
    }
    componentDidMount(){
        console.log(this.currentDom)
    }
    render= () => <div ref={ this.currentDom } >ref對象模式獲取元素或組件</div>
}

打印

React.createRef 的底層邏輯很簡單。下面一起來看一下:

react/src/ReactCreateRef.js

export function createRef() {
  const refObject = {
    current: null,
  }
  return refObject;
}

createRef 一般用于類組件創(chuàng)建 Ref 對象,可以將 Ref 對象綁定在類組件實例上,這樣更方便后續(xù)操作 Ref。
注意:不要在函數(shù)組件中使用 createRef,否則會造成 Ref 對象內(nèi)容丟失等情況。

函數(shù)組件

函數(shù)組件創(chuàng)建 ref ,可以用 hooks 中的 useRef 來達(dá)到同樣的效果。

export default function Index(){
    const currentDom = React.useRef(null)
    React.useEffect(()=>{
        console.log( currentDom.current ) // div
    },[])
    return  <div ref={ currentDom } >ref對象模式獲取元素或組件</div>
}

react-reconciler/ReactFiberHooks.js

function mountRef<T>(initialValue: T): {current: T} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

useRef 返回一個可變的 ref 對象,其 current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變。
這個 ref 對象只有一個current屬性,你把一個東西保存在內(nèi),它的地址一直不會變。

拓展和總結(jié)

useRef 底層邏輯是和 createRef 差不多,就是 ref 保存位置不相同,類組件有一個實例 instance 能夠維護(hù)像 ref 這種信息,但是由于函數(shù)組件每次更新都是一次新的開始,所有變量重新聲明,所以 useRef 不能像 createRef 把 ref 對象直接暴露出去,如果這樣每一次函數(shù)組件執(zhí)行就會重新聲明 ref,此時 ref 就會隨著函數(shù)組件執(zhí)行被重置,這就解釋了在函數(shù)組件中為什么不能用 createRef 的原因。
為了解決這個問題,hooks 和函數(shù)組件對應(yīng)的 fiber 對象建立起關(guān)聯(lián),將 useRef 產(chǎn)生的 ref 對象掛到函數(shù)組件對應(yīng)的 fiber 上,函數(shù)組件每次執(zhí)行,只要組件不被銷毀,函數(shù)組件對應(yīng)的 fiber 對象一直存在,所以 ref 等信息就會被保存下來。

react 對 ref 屬性的處理-標(biāo)記 ref

首先我們先明確一個問題就是 DOM 元素和組件實例必須用 ref 來獲取嗎?答案肯定是否定的,比如 react 還提供了一個 findDOMNode 方法可以獲取 dom 元素,有興趣的可以私下去了解一下。不過通過 ref 的方式來獲取還是最常用的一種方式。

類組件獲取 ref 二種方式

因為 ref 屬性是字符串的這種方式,react 高版本已經(jīng)舍棄掉,這里就不再介紹了。

ref 屬性是個函數(shù)

class Children extends React.Component{  
    render=()=><div>hello,world</div>
}
/* TODO: Ref屬性是一個函數(shù) */
export default class Index extends React.Component{
    currentDom = null
    currentComponentInstance = null
    componentDidMount(){
        console.log(this.currentDom)
        console.log(this.currentComponentInstance)
    }
    render=()=> <div>
        <div ref={(node)=> this.currentDom = node }  >Ref屬性是個函數(shù)</div>
        <Children ref={(node) => this.currentComponentInstance = node  }  />
    </div>
}

打印

當(dāng)用一個函數(shù)來標(biāo)記 ref 的時候,將作為 callback 形式,等到真實 DOM 創(chuàng)建階段,執(zhí)行 callback ,獲取的 DOM 元素或組件實例,將以回調(diào)函數(shù)第一個參數(shù)形式傳入,所以可以像上述代碼片段中,用組件實例下的屬性 currentDom 和 currentComponentInstance 來接收真實 DOM 和組件實例。

ref 屬性是一個 ref 對象

class Children extends React.Component{  
    render=()=><div>hello,world</div>
}
export default class Index extends React.Component{
    currentDom = React.createRef(null)
    currentComponentInstance = React.createRef(null)
    componentDidMount(){
        console.log(this.currentDom)
        console.log(this.currentComponentInstance)
    }
    render=()=> <div>
         <div ref={ this.currentDom }  >Ref對象模式獲取元素或組件</div>
        <Children ref={ this.currentComponentInstance }  />
   </div>
}

函數(shù)組件獲取 ref 的方式(useRef)

const Children = () => {  
  return <div>hello,world</div>
}
const Index = () => {
  const currentDom = React.useRef(null)
  const currentComponentInstance = React.useRef(null)
  React.useEffect(()=>{
    console.log( currentDom ) 
    console.log( currentComponentInstance ) 
  },[])
  return (
    <div>
      <div ref={ currentDom }  >通過useRef獲取元素或者組件</div>
      <Children ref={ currentComponentInstance }  />
   </div>
  )
}

ref 的拓展用法

不得不說的 forwardRef

forwardRef 的初衷就是解決 ref 不能跨層級捕獲和傳遞的問題。 forwardRef 接受了父級元素標(biāo)記的 ref 信息,并把它轉(zhuǎn)發(fā)下去,使得子組件可以通過 props 來接受到上一層級或者是更上層級的 ref 。

跨層級獲取

我們把上面的例子改一下

獲取子元素的dom

const Children = React.forwardRef((props, ref) => {
  return <div ref={ref}>hello,world</div>
}) 
const Index = () => {
  const currentDom = React.useRef(null)
  const currentComponentInstance = React.useRef(null)
  React.useEffect(()=>{
    console.log( currentDom ) 
    console.log( currentComponentInstance ) 
  },[])
  return (
    <div>
      <div ref={ currentDom }  >通過useRef獲取元素或者組件</div>
      <Children ref={ currentComponentInstance }  />
   </div>
  )

想要在 GrandFather 組件通過標(biāo)記 ref ,來獲取孫組件 Son 的組件實例。

// 孫組件
function Son (props){
  const { grandRef } = props
  return <div>
      <div> i am alien </div>
      <span ref={grandRef} >這個是想要獲取元素</span>
  </div>
}
// 父組件
class Father extends React.Component{
  render(){
      return <div>
          <Son grandRef={this.props.grandRef}  />
      </div>
  }
}
const NewFather = React.forwardRef((props,ref)=> <Father grandRef={ref}  {...props} />)
// 爺組件
class GrandFather extends React.Component{
  node = null 
  componentDidMount(){
      console.log(this.node) // span #text 這個是想要獲取元素
  }
  render(){
      return <div>
          <NewFather ref={(node)=> this.node = node } />
      </div>
  }
}

合并轉(zhuǎn)發(fā)ref

通過 forwardRef 轉(zhuǎn)發(fā)的 ref 不要理解為只能用來直接獲取組件實例,DOM 元素,也可以用來傳遞合并之后的自定義的 ref

場景:想通過 Home 綁定 ref ,來獲取子組件 Index 的實例 index ,dom 元素 button ,以及孫組件 Form 的實例

// 表單組件
class Form extends React.Component{
  render(){
     return <div>{...}</div>
  }
}
// index 組件
class Index extends React.Component{ 
  componentDidMount(){
      const { forwardRef } = this.props
      forwardRef.current={
          form:this.form,      // 給form組件實例 ,綁定給 ref form屬性 
          index:this,          // 給index組件實例 ,綁定給 ref index屬性 
          button:this.button,  // 給button dom 元素,綁定給 ref button屬性 
      }
  }
  form = null
  button = null
  render(){
      return <div   > 
        <button ref={(button)=> this.button = button }  >點擊</button>
        <Form  ref={(form) => this.form = form }  />  
    </div>
  }
}
const ForwardRefIndex = React.forwardRef(( props,ref )=><Index  {...props} forwardRef={ref}  />)
// home 組件
const Home = () => {
  const ref = useRef(null)
   useEffect(()=>{
       console.log(ref.current)
   },[])
  return <ForwardRefIndex ref={ref} />
}

高階組件轉(zhuǎn)發(fā)

如果通過高階組件包裹一個原始類組件,就會產(chǎn)生一個問題,如果高階組件 HOC 沒有處理 ref ,那么由于高階組件本身會返回一個新組件,所以當(dāng)使用 HOC 包裝后組件的時候,標(biāo)記的 ref 會指向 HOC 返回的組件,而并不是 HOC 包裹的原始類組件,為了解決這個問題,forwardRef 可以對 HOC 做一層處理。

function HOC(Component){
  class Wrap extends React.Component{
     render(){
        const { forwardedRef ,...otherprops  } = this.props
        return <Component ref={forwardedRef}  {...otherprops}  />
     }
  }
  return  React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> ) 
}
class Index1 extends React.Component{
  state={
    name: '222'
  }
  render(){
    return <div>hello,world</div>
  }
}
const HocIndex =  HOC(Index1)
const AppIndex = ()=>{
  const node = useRef(null)
  useEffect(()=>{
    console.log(node.current)  /* Index 組件實例  */ 
  },[])
  return <div><HocIndex ref={node}  /></div>
}

源碼位置 react/src/forwardRef.js

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}

ref 實現(xiàn)組件通信

如果有種場景不想通過父組件 render 改變 props 的方式,來觸發(fā)子組件的更新,也就是子組件通過 state 單獨(dú)管理數(shù)據(jù)層,針對這種情況父組件可以通過 ref 模式標(biāo)記子組件實例,從而操縱子組件方法,這種情況通常發(fā)生在一些數(shù)據(jù)層托管的組件上,比如  表單,經(jīng)典案例可以參考 antd 里面的 form 表單,暴露出對外的 resetFields , setFieldsValue 等接口,可以通過表單實例調(diào)用這些 API 。

/* 子組件 */
class Son extends React.PureComponent{
  state={
     fatherMes:'',
     sonMes:'我是子組件'
  }
  fatherSay=(fatherMes)=> this.setState({ fatherMes  }) /* 提供給父組件的API */
  render(){
      const { fatherMes, sonMes } = this.state
      return <div className="sonbox" >
          <p>父組件對我說:{ fatherMes }</p>
          <button className="searchbtn" onClick={ ()=> this.props.toFather(sonMes) }  >to father</button>
      </div>
  }
}
/* 父組件 */
function Father(){
  const [ sonMes , setSonMes ] = React.useState('') 
  const sonInstance = React.useRef(null) /* 用來獲取子組件實例 */
  const toSon =()=> sonInstance.current.fatherSay('我是父組件') /* 調(diào)用子組件實例方法,改變子組件state */
  return <div className="box" >
      <div className="title" >父組件</div>
      <p>子組件對我說:{ sonMes }</p>
      <button className="searchbtn"  onClick={toSon}  >to son</button>
      <Son ref={sonInstance} toFather={setSonMes} />
  </div>
}

子組件暴露方法 fatherSay 供父組件使用,父組件通過調(diào)用方法可以設(shè)置子組件展示內(nèi)容。
父組件提供給子組件 toFather,子組件調(diào)用,改變父組件展示內(nèi)容,實現(xiàn)父 <-> 子 雙向通信。

函數(shù)組件 forwardRef + useImperativeHandle

對于函數(shù)組件,本身是沒有實例的,但是 React Hooks 提供了,useImperativeHandle 一方面第一個參數(shù)接受父組件傳遞的 ref 對象,另一方面第二個參數(shù)是一個函數(shù),函數(shù)返回值,作為 ref 對象獲取的內(nèi)容。一起看一下 useImperativeHandle 的基本使用。

useImperativeHandle 接受三個參數(shù):

第一個參數(shù) ref : 接受 forWardRef 傳遞過來的 ref 。
第二個參數(shù) createHandle :處理函數(shù),返回值作為暴露給父組件的 ref 對象。
第三個參數(shù) deps : 依賴項 deps,依賴項更改形成新的 ref 對象。

const Son = React.forwardRef((props, ref) => {
  const state = {
      sonMes:'我是子組件'
  }
  const [fatherMes, setFatherMes] = React.useState('')
    useImperativeHandle(ref,()=>{
      const handleRefs = {
        fatherSay(fatherMss){            
          setFatherMes(fatherMss)
        }
      }
      return handleRefs
  },[])
  const { sonMes } = state
  return (
    <div>
      <p>父組件對我說: {fatherMes}</p>
      <button  onClick={ ()=> props.toFather(sonMes) }  >to father</button>
    </div>
  ) 
})
/* 父組件 */
function Father(){
  const [ sonMes , setSonMes ] = React.useState('') 
  const sonInstance = React.useRef(null) /* 用來獲取子組件實例 */
  const toSon = () => {
    sonInstance.current.fatherSay('我是父組件')
  }
  return <div className="box" >
      <div className="title" >父組件</div>
      <p>子組件對我說:{ sonMes }</p>
      <button className="searchbtn"  onClick={toSon}  >to son</button>
      <Son ref={sonInstance} toFather={setSonMes} />
  </div>
}

forwardRef + useImperativeHandle 可以完全讓函數(shù)組件也能流暢的使用 Ref 通信。其原理圖如下所示:

函數(shù)組件緩存數(shù)據(jù)

函數(shù)組件每一次 render ,函數(shù)上下文會重新執(zhí)行,那么有一種情況就是,在執(zhí)行一些事件方法改變數(shù)據(jù)或者保存新數(shù)據(jù)的時候,有沒有必要更新視圖,有沒有必要把數(shù)據(jù)放到 state 中。如果視圖層更新不依賴想要改變的數(shù)據(jù),那么 state 改變帶來的更新效果就是多余的。這時候更新無疑是一種性能上的浪費(fèi)。

這種情況下,useRef 就派上用場了,上面講到過,useRef 可以創(chuàng)建出一個 ref 原始對象,只要組件沒有銷毀,ref 對象就一直存在,那么完全可以把一些不依賴于視圖更新的數(shù)據(jù)儲存到 ref 對象中。這樣做的好處有兩個:

第一個能夠直接修改數(shù)據(jù),不會造成函數(shù)組件冗余的更新作用。
第二個 useRef 保存數(shù)據(jù),如果有 useEffect ,useMemo 引用 ref 對象中的數(shù)據(jù),無須將 ref 對象添加成 dep 依賴項,因為 useRef 始終指向一個內(nèi)存空間,所以這樣一點好處是可以隨時訪問到變化后的值。

清除定時器

const App = () => {
  const [count, setCount] = useState(0)
  let timer;
  useEffect(() => {
    timer = setInterval(() => {
      console.log('觸發(fā)了');
    }, 1000);
  },[]);
  const clearTimer = () => {
    clearInterval(timer);
  }
  return (
    <>
     <button onClick={() => {setCount(count + 1)}}>點了{(lán)count}次</button>
      <button onClick={clearTimer}>停止</button>
    </>)
}

但是上面這個寫法有個巨大的問題,如果這個 App 組件里有state變化或者他的父組件重新 render 等原因?qū)е逻@個 App 組件重新 render 的時候,我們會發(fā)現(xiàn),點擊按鈕停止,定時器依然會不斷的在控制臺打印,定時器清除事件無效了。
為什么呢?因為組件重新渲染之后,這里的 timer 以及 clearTimer 方法都會重新創(chuàng)建,timer 已經(jīng)不是定時器的變量了。
所以對于定時器,我們都會使用 useRef 來定義變量。

const App = () => {
  const [count, setCount] = useState(0)
  const timer = useRef();
  useEffect(() => {
    timer.current = setInterval(() => {
      console.log('觸發(fā)了');
    }, 1000);
  },[]);
  const clearTimer = () => {
    clearInterval(timer.current);
  }
  return (
    <>
      <button onClick={() => {setCount(count + 1)}}>點了{(lán)count}次</button>
      <button onClick={clearTimer}>停止</button>
    </>)
}

ref 原理探究

對于 ref 標(biāo)簽引用,React 是如何處理的呢? 接下來先來看看一段 demo 代碼

class DomRef extends React.Component{
  state={ num:0 }
  node = null
  render(){
      return <div >
          <div ref={(node)=>{
             this.node = node
             console.log('此時的參數(shù)是什么:', this.node )
          }}  >ref元素節(jié)點</div>
          <button onClick={()=> this.setState({ num: this.state.num + 1  }) } >點擊</button>
      </div>
  }
}

控制臺輸出結(jié)果

提問: 第一次打印為 null ,第二次才是 div ,為什么會這樣呢? 這樣的意義又是什么呢?

ref 執(zhí)行的時機(jī)和處理邏輯

根據(jù) React 的生命周期可以知道,更新的兩個階段 render 階段和 commit 階段,對于整個 ref 的處理,都是在 commit 階段發(fā)生的。之前了解過 commit 階段會進(jìn)行真正的 Dom 操作,此時 ref 就是用來獲取真實的 DOM 以及組件實例的,所以需要 commit 階段處理。
但是對于 ref 處理函數(shù),React 底層用兩個方法處理:commitDetachRef 和 commitAttachRef ,上述兩次 console.log 一次為 null,一次為 div 就是分別調(diào)用了上述的方法。

這兩次正好,一次在 DOM 更新之前,一次在 DOM 更新之后。

第一階段:一次更新中,在 commit 的 mutation 階段, 執(zhí)行commitDetachRef,commitDetachRef 會清空之前ref值,使其重置為 null。
源碼先來看一下

react-reconciler/src/ReactFiberCommitWork.js

function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') { /* function獲取方式。 */
      currentRef(null); 
    } else {   /* Ref對象獲取方式 */
      currentRef.current = null;
    }
  }
}

第二階段:DOM 更新階段,這個階段會根據(jù)不同的 effect 標(biāo)簽,真實的操作 DOM 。
第三階段:layout 階段,在更新真實元素節(jié)點之后,此時需要更新 ref

react-reconciler/src/ReactFiberCommitWork.js

function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostComponent: //元素節(jié)點 獲取元素
        instanceToUse = getPublicInstance(instance);
        break;
      default:  // 類組件直接使用實例
        instanceToUse = instance;
    }
    if (typeof ref === 'function') {
      ref(instanceToUse);  //* function 和 字符串獲取方式。 */
    } else {
      ref.current = instanceToUse; /* ref對象方式 */
    }
  }
}

這一階段,主要判斷 ref 獲取的是組件還是 DOM 元素標(biāo)簽,如果 DOM 元素,就會獲取更新之后最新的 DOM 元素。上面流程中講了二種獲取 ref 的方式。 如果是 函數(shù)式 ref={(node)=> this.node = node } 會執(zhí)行 ref 函數(shù),重置新的 ref 。如果是 ref 對象方式,會更新 ref 對象的 current 屬性。達(dá)到更新 ref 對象的目的。

ref 的處理特性

接下來看一下 ref 的一些特性,首先來看一下,上述沒有提及的一個問題,React 被 ref 標(biāo)記的 fiber,那么每一次 fiber 更新都會調(diào)用 commitDetachRef 和 commitAttachRef 更新 Ref 嗎 ?
答案是否定的,只有在 ref 更新的時候,才會調(diào)用如上方法更新 ref ,究其原因還要從如上兩個方法的執(zhí)行時期說起

更新 ref

在 commit 階段 commitDetachRef 和 commitAttachRef 是在什么條件下被執(zhí)行的呢 ? 來一起看一下:
commitDetachRef 調(diào)用時機(jī)

react-reconciler/src/ReactFiberWorkLoop.js

function commitMutationEffects(){
     if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }
}

commitAttachRef 調(diào)用時機(jī)

function commitLayoutEffects(){
     if (effectTag &amp; Ref) {
      commitAttachRef(nextEffect);
    }
}

從上可以清晰的看到只有含有 ref tag 的時候,才會執(zhí)行更新 ref,那么是每一次更新都會打 ref tag 嗎? 跟著我的思路往下看,什么時候標(biāo)記的 ref .

react-reconciler/src/ReactFiberBeginWork.js

function markRef(current: Fiber | null, workInProgress: Fiber) {
  const ref = workInProgress.ref;
  if (
    (current === null && ref !== null) ||      // 初始化的時候
    (current !== null && current.ref !== ref)  // ref 指向發(fā)生改變
  ) {
    workInProgress.effectTag |= Ref;
  }
}

首先 markRef 方法執(zhí)行在兩種情況下:
第一種就是類組件的更新過程
第二種就是更新 HostComponent 的時候
markRef 會在以下兩種情況下給 effectTag 標(biāo)記 ref,只有標(biāo)記了 ref tag 才會有后續(xù)的 commitAttachRef 和 commitDetachRef 流程。( current 為當(dāng)前調(diào)和的 fiber 節(jié)點 )

第一種 current === null && ref !== null:就是在 fiber 初始化的時候,第一次 ref 處理的時候,是一定要標(biāo)記 ref 的。
第二種 current !== null && current.ref !== ref:就是 fiber 更新的時候,但是 ref 對象的指向變了。

所以回到最初的那個 DomRef 組件,為什么每一次按鈕,都會打印 ref ,那么也就是 ref 的回調(diào)函數(shù)執(zhí)行了,ref 更新了。每一次更新的時候,都給 ref 賦值了新的函數(shù),那么 markRef 中就會判斷成 current.ref !== ref,所以就會重新打 Ref 標(biāo)簽,那么在 commit 階段,就會更新 ref 執(zhí)行 ref 回調(diào)函數(shù)了。
要想解決這個問題,我們把 DomRef 組件做下修改:

 class DomRef extends React.Component{
    state={ num:0 }
    node = null
    getDom= (node)=>{
        this.node = node
        console.log('此時的參數(shù)是什么:', this.node )
     }
    render(){
        return <div >
            <div ref={this.getDom}>ref元素節(jié)點</div>
            <button onClick={()=> this.setState({ num: this.state.num + 1  })} >點擊</button>
        </div>
    }
}

function DomRef () {
  const [num, setNum] = useState(0)
  const node = useRef(null)
      return (
      <div >
          <div ref={node}>ref元素節(jié)點</div>
          <button onClick={()=> {
            setNum(num + 1)
            console.log(node)
          }} >點擊</button>
      </div>
    )
}

卸載 ref

上述講了 ref 更新階段的特點,接下來分析一下當(dāng)組件或者元素卸載的時候,ref 的處理邏輯是怎么樣的。

react-reconciler/src/ReactFiberCommitWork.js

function safelyDetachRef(current) {
  const ref = current.ref;
  if (ref !== null) {
    if (typeof ref === 'function') {  // 函數(shù)式 
        ref(null)
    } else {
      ref.current = null;  // ref 對象
    }
  }
}

被卸載的 fiber 會被打成 Deletion effect tag ,然后在 commit 階段會進(jìn)行 commitDeletion 流程。對于有 ref 標(biāo)記的 ClassComponent (類組件) 和 HostComponent (元素),會統(tǒng)一走 safelyDetachRef 流程,這個方法就是用來卸載 ref。

總結(jié)

  • ref 對象的二種創(chuàng)建方式
  • 兩種獲取 ref 方法
  • 介紹了一下forwardRef 用法
  • ref 組件通信-函數(shù)組件和類組件兩種方式
  • useRef 緩存數(shù)據(jù)的用法
  • ref 的處理邏輯原理

到此這篇關(guān)于React中的 ref 及原理淺析的文章就介紹到這了,更多相關(guān)React ref 原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React Native提供自動完成的下拉菜單的方法示例

    React Native提供自動完成的下拉菜單的方法示例

    這篇文章主要為大家介紹了React Native提供自動完成的下拉菜單的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 為react組件庫添加typescript類型提示的方法

    為react組件庫添加typescript類型提示的方法

    這篇文章主要介紹了為react組件庫添加typescript類型提示,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • React開發(fā)進(jìn)階redux saga使用原理詳解

    React開發(fā)進(jìn)階redux saga使用原理詳解

    這篇文章主要為大家介紹了React開發(fā)進(jìn)階redux saga使用原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • webpack4+react多頁面架構(gòu)的實現(xiàn)

    webpack4+react多頁面架構(gòu)的實現(xiàn)

    webpack在單頁面打包上應(yīng)用廣泛,以create-react-app為首的腳手架眾多。這篇文章主要介紹了webpack4+react多頁面架構(gòu)的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • React事件綁定的方式詳解

    React事件綁定的方式詳解

    react事件綁定時。this并不會指向當(dāng)前DOM元素。往往使用bind來改變this指向,今天通過本文給大家介紹React事件綁定的方式,感興趣的朋友
    2021-07-07
  • 淺談react受控組件與非受控組件(小結(jié))

    淺談react受控組件與非受控組件(小結(jié))

    本篇文章主要介紹了淺談react受控組件與非受控組件(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • React Native使用Modal自定義分享界面的示例代碼

    React Native使用Modal自定義分享界面的示例代碼

    本篇文章主要介紹了React Native使用Modal自定義分享界面的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • 在create-react-app中使用sass的方法示例

    在create-react-app中使用sass的方法示例

    這篇文章主要介紹了在create-react-app中使用sass的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • React實現(xiàn)導(dǎo)出excel文件的操作步驟

    React實現(xiàn)導(dǎo)出excel文件的操作步驟

    在React項目的TypeScript文件中,因為原生的JavaScript或TypeScript并沒有提供直接的Excel導(dǎo)出功能,常用的Excel導(dǎo)出方法通常涉及使用第三方庫,本文介紹了React實現(xiàn)導(dǎo)出excel文件的操作步驟,需要的朋友可以參考下
    2024-12-12
  • 使用useMutation和React Query發(fā)布數(shù)據(jù)demo

    使用useMutation和React Query發(fā)布數(shù)據(jù)demo

    這篇文章主要為大家介紹了使用useMutation和React Query發(fā)布數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評論