最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React中的?ref?及原理解析

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

前言

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

基本概念和使用

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

ref 對(duì)象的創(chuàng)建

什么是 ref ?

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

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

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

類組件 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對(duì)象模式獲取元素或組件</div>
}

打印

React.createRef 的底層邏輯很簡(jiǎn)單。下面一起來(lái)看一下:

react/src/ReactCreateRef.js

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

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

函數(shù)組件

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

export default function Index(){
    const currentDom = React.useRef(null)
    React.useEffect(()=>{
        console.log( currentDom.current ) // div
    },[])
    return  <div ref={ currentDom } >ref對(duì)象模式獲取元素或組件</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 返回一個(gè)可變的 ref 對(duì)象,其 current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變。
這個(gè) ref 對(duì)象只有一個(gè)current屬性,你把一個(gè)東西保存在內(nèi),它的地址一直不會(huì)變。

拓展和總結(jié)

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

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

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

類組件獲取 ref 二種方式

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

ref 屬性是個(gè)函數(shù)

class Children extends React.Component{  
    render=()=><div>hello,world</div>
}
/* TODO: Ref屬性是一個(gè)函數(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屬性是個(gè)函數(shù)</div>
        <Children ref={(node) => this.currentComponentInstance = node  }  />
    </div>
}

打印

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

ref 屬性是一個(gè) ref 對(duì)象

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對(duì)象模式獲取元素或組件</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 }  >通過(guò)useRef獲取元素或者組件</div>
      <Children ref={ currentComponentInstance }  />
   </div>
  )
}

ref 的拓展用法

不得不說(shuō)的 forwardRef

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

跨層級(jí)獲取

我們把上面的例子改一下

獲取子元素的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 }  >通過(guò)useRef獲取元素或者組件</div>
      <Children ref={ currentComponentInstance }  />
   </div>
  )

想要在 GrandFather 組件通過(guò)標(biāo)記 ref ,來(lái)獲取孫組件 Son 的組件實(shí)例。

// 孫組件
function Son (props){
  const { grandRef } = props
  return <div>
      <div> i am alien </div>
      <span ref={grandRef} >這個(gè)是想要獲取元素</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 這個(gè)是想要獲取元素
  }
  render(){
      return <div>
          <NewFather ref={(node)=> this.node = node } />
      </div>
  }
}

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

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

場(chǎng)景:想通過(guò) Home 綁定 ref ,來(lái)獲取子組件 Index 的實(shí)例 index ,dom 元素 button ,以及孫組件 Form 的實(shí)例

// 表單組件
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組件實(shí)例 ,綁定給 ref form屬性 
          index:this,          // 給index組件實(shí)例 ,綁定給 ref index屬性 
          button:this.button,  // 給button dom 元素,綁定給 ref button屬性 
      }
  }
  form = null
  button = null
  render(){
      return <div   > 
        <button ref={(button)=> this.button = button }  >點(diǎn)擊</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ā)

如果通過(guò)高階組件包裹一個(gè)原始類組件,就會(huì)產(chǎn)生一個(gè)問(wèn)題,如果高階組件 HOC 沒(méi)有處理 ref ,那么由于高階組件本身會(huì)返回一個(gè)新組件,所以當(dāng)使用 HOC 包裝后組件的時(shí)候,標(biāo)記的 ref 會(huì)指向 HOC 返回的組件,而并不是 HOC 包裹的原始類組件,為了解決這個(gè)問(wèn)題,forwardRef 可以對(duì) 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 組件實(shí)例  */ 
  },[])
  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 實(shí)現(xiàn)組件通信

如果有種場(chǎng)景不想通過(guò)父組件 render 改變 props 的方式,來(lái)觸發(fā)子組件的更新,也就是子組件通過(guò) state 單獨(dú)管理數(shù)據(jù)層,針對(duì)這種情況父組件可以通過(guò) ref 模式標(biāo)記子組件實(shí)例,從而操縱子組件方法,這種情況通常發(fā)生在一些數(shù)據(jù)層托管的組件上,比如  表單,經(jīng)典案例可以參考 antd 里面的 form 表單,暴露出對(duì)外的 resetFields , setFieldsValue 等接口,可以通過(guò)表單實(shí)例調(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>父組件對(duì)我說(shuō):{ 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) /* 用來(lái)獲取子組件實(shí)例 */
  const toSon =()=> sonInstance.current.fatherSay('我是父組件') /* 調(diào)用子組件實(shí)例方法,改變子組件state */
  return <div className="box" >
      <div className="title" >父組件</div>
      <p>子組件對(duì)我說(shuō):{ sonMes }</p>
      <button className="searchbtn"  onClick={toSon}  >to son</button>
      <Son ref={sonInstance} toFather={setSonMes} />
  </div>
}

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

函數(shù)組件 forwardRef + useImperativeHandle

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

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

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

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>父組件對(duì)我說(shuō): {fatherMes}</p>
      <button  onClick={ ()=> props.toFather(sonMes) }  >to father</button>
    </div>
  ) 
})
/* 父組件 */
function Father(){
  const [ sonMes , setSonMes ] = React.useState('') 
  const sonInstance = React.useRef(null) /* 用來(lái)獲取子組件實(shí)例 */
  const toSon = () => {
    sonInstance.current.fatherSay('我是父組件')
  }
  return <div className="box" >
      <div className="title" >父組件</div>
      <p>子組件對(duì)我說(shuō):{ 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ù)上下文會(huì)重新執(zhí)行,那么有一種情況就是,在執(zhí)行一些事件方法改變數(shù)據(jù)或者保存新數(shù)據(jù)的時(shí)候,有沒(méi)有必要更新視圖,有沒(méi)有必要把數(shù)據(jù)放到 state 中。如果視圖層更新不依賴想要改變的數(shù)據(jù),那么 state 改變帶來(lái)的更新效果就是多余的。這時(shí)候更新無(wú)疑是一種性能上的浪費(fèi)。

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

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

清除定時(shí)器

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)}}>點(diǎn)了{(lán)count}次</button>
      <button onClick={clearTimer}>停止</button>
    </>)
}

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

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)}}>點(diǎn)了{(lán)count}次</button>
      <button onClick={clearTimer}>停止</button>
    </>)
}

ref 原理探究

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

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

控制臺(tái)輸出結(jié)果

提問(wèn): 第一次打印為 null ,第二次才是 div ,為什么會(huì)這樣呢? 這樣的意義又是什么呢?

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

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

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

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

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對(duì)象獲取方式 */
      currentRef.current = null;
    }
  }
}

第二階段:DOM 更新階段,這個(gè)階段會(huì)根據(jù)不同的 effect 標(biāo)簽,真實(shí)的操作 DOM 。
第三階段:layout 階段,在更新真實(shí)元素節(jié)點(diǎn)之后,此時(shí)需要更新 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é)點(diǎn) 獲取元素
        instanceToUse = getPublicInstance(instance);
        break;
      default:  // 類組件直接使用實(shí)例
        instanceToUse = instance;
    }
    if (typeof ref === 'function') {
      ref(instanceToUse);  //* function 和 字符串獲取方式。 */
    } else {
      ref.current = instanceToUse; /* ref對(duì)象方式 */
    }
  }
}

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

ref 的處理特性

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

更新 ref

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

react-reconciler/src/ReactFiberWorkLoop.js

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

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

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

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

react-reconciler/src/ReactFiberBeginWork.js

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

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

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

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

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

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

卸載 ref

上述講了 ref 更新階段的特點(diǎn),接下來(lái)分析一下當(dāng)組件或者元素卸載的時(shí)候,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 對(duì)象
    }
  }
}

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

總結(jié)

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

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

相關(guān)文章

  • 使用React實(shí)現(xiàn)輪播效果組件示例代碼

    使用React實(shí)現(xiàn)輪播效果組件示例代碼

    React剛出來(lái)不久,組件還比較少,不像jquery那樣已經(jīng)有很多現(xiàn)成的插件了,于是自己寫了一個(gè)基于React的輪播效果組件,現(xiàn)在分享給大家,有需要的可以參考借鑒。
    2016-09-09
  • React?Fiber?鏈表操作及原理示例詳解

    React?Fiber?鏈表操作及原理示例詳解

    這篇文章主要為大家介紹了React?Fiber?鏈表操作原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • 解決React報(bào)錯(cuò)Rendered more hooks than during the previous render

    解決React報(bào)錯(cuò)Rendered more hooks than during

    這篇文章主要為大家介紹了React報(bào)錯(cuò)Rendered more hooks than during the previous render解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • react中的DOM操作實(shí)現(xiàn)

    react中的DOM操作實(shí)現(xiàn)

    某些情況下需要在典型數(shù)據(jù)流外強(qiáng)制修改子代。要修改的子代可以是 React 組件實(shí)例,也可以是 DOM 元素。這時(shí)就要用到refs來(lái)操作DOM,本文詳細(xì)的介紹一下使用,感興趣的可以了解一下
    2021-06-06
  • react基于Ant Desgin Upload實(shí)現(xiàn)導(dǎo)入導(dǎo)出

    react基于Ant Desgin Upload實(shí)現(xiàn)導(dǎo)入導(dǎo)出

    本文主要介紹了react基于Ant Desgin Upload實(shí)現(xiàn)導(dǎo)入導(dǎo)出,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • 在?React?項(xiàng)目中全量使用?Hooks的方法

    在?React?項(xiàng)目中全量使用?Hooks的方法

    這篇文章主要介紹了在?React?項(xiàng)目中全量使用?Hooks,使用 Hooks 能為開發(fā)提升不少效率,但并不代表就要拋棄 Class Component,依舊還有很多場(chǎng)景我們還得用到它,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • React19 Diff 算法的具體實(shí)現(xiàn)

    React19 Diff 算法的具體實(shí)現(xiàn)

    本文主要介紹了React19 Diff 算法的具體實(shí)現(xiàn),該算法用于高效地更新虛擬DOM,它通過(guò)優(yōu)化傳統(tǒng)樹diff算法,將復(fù)雜度從O(n^3)降低到O(n),感興趣的可以了解一下
    2026-05-05
  • React自定義Hook-useForkRef的具體使用

    React自定義Hook-useForkRef的具體使用

    本文主要介紹了React自定義Hook-useForkRef的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • React前端渲染優(yōu)化--父組件導(dǎo)致子組件重復(fù)渲染的問(wèn)題

    React前端渲染優(yōu)化--父組件導(dǎo)致子組件重復(fù)渲染的問(wèn)題

    本篇文章是針對(duì)父組件導(dǎo)致子組件重復(fù)渲染的優(yōu)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React純前端模擬實(shí)現(xiàn)登錄鑒權(quán)

    React純前端模擬實(shí)現(xiàn)登錄鑒權(quán)

    這篇文章主要為大家詳細(xì)介紹了React純前端模擬實(shí)現(xiàn)登錄鑒權(quán)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04

最新評(píng)論

镇江市| 五寨县| 托里县| 阜宁县| 德化县| 宁国市| 舟山市| 于田县| 永宁县| 天祝| 芒康县| 新乡市| 禹城市| 和林格尔县| 富源县| 三都| 汶川县| 崇阳县| 淮北市| 开原市| 株洲县| 和田县| 德清县| 湘潭县| 尼勒克县| 石门县| 寻乌县| 双桥区| 广河县| 调兵山市| 双牌县| 云林县| 巩义市| 新龙县| 板桥市| 龙江县| 靖江市| 双城市| 拜泉县| 万荣县| 东港市|