<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          關(guān)于ref的一切

          共 4699字,需瀏覽 10分鐘

           ·

          2020-09-08 16:12

          作為React開發(fā)者,你能回答如下幾個(gè)問題么?

          1. 為什么string類型的ref prop將會(huì)被廢棄?

          2. function類型的ref prop會(huì)在什么時(shí)機(jī)被調(diào)用?

          3. React.createRefuseRef的返回值有什么不同?

          其實(shí),這三個(gè)問題中的ref包含兩個(gè)不同概念:

          • 不管是stringfunction類型或是React.createRef、useRef創(chuàng)建的ref,都是作為數(shù)據(jù)結(jié)構(gòu)看待

          • 問題2探討的時(shí)機(jī)是將ref作為生命周期看待

          接下來本文會(huì)分別從數(shù)據(jù)結(jié)構(gòu)、生命周期兩個(gè)角度探討ref。

          這,就是關(guān)于ref的一切。

          ref的數(shù)據(jù)結(jié)構(gòu)

          為什么string類型的ref prop將會(huì)被廢棄?

          string類型的ref使用方式如下:

          點(diǎn)擊input標(biāo)簽會(huì)打印inputvalue。

          class?Foo?extends?Component?{
          ??render()?{
          ????return?(
          ??????<input
          ????????onClick={()?=>
          ?this.action()}?
          ????????ref='input'?
          ??????/>
          ????);
          ??}
          ??action()?{
          ????console.log(this.refs.input.value);
          ??}
          }

          string類型ref prop最主要的兩個(gè)問題是:

          1. 由于是string的寫法,無法直接獲得this的指向。

          所以,React需要持續(xù)追蹤當(dāng)前render的組件。這會(huì)讓React在性能上變慢。

          1. 當(dāng)使用render回調(diào)函數(shù)的開發(fā)模式,獲得ref的組件實(shí)例可能與預(yù)期不同。

          比如:

          class?App?extends?React.Component?{
          ??renderRow?=?(index)?=>?{
          ????//?ref會(huì)綁定到DataTable組件實(shí)例,而不是App組件實(shí)例上
          ????return?;

          ????//?如果使用function類型ref,則不會(huì)有這個(gè)問題
          ????//?return??this['input-'?+?index]?=?input}?/>;
          ??}
          ?
          ??render()?{
          ????return?
          ??}
          }

          還有其他原因使React團(tuán)隊(duì)決定在未來放棄string Ref,詳見#1373[1]與#8333[2]。

          React.createRef

          我們直接看React.createRef的源碼:

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

          可見,ref對(duì)象就是僅僅是包含current屬性的普通對(duì)象。

          useRef

          為了驗(yàn)證這個(gè)觀點(diǎn),我們?cè)倏?code style="font-size:14px;color:rgb(30,107,184);background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">useRef的源碼。

          對(duì)于mountupdate,useRef分別對(duì)應(yīng)兩個(gè)函數(shù)。

          對(duì)于hook如何保存數(shù)據(jù)如果不了解,可以看本系列第一篇文章關(guān)于useState的一切

          function?mountRef<T>(initialValue:?T):?{|current:?T|}?{
          ??//?獲取當(dāng)前useRef?hook
          ??const?hook?=?mountWorkInProgressHook();
          ??//?創(chuàng)建ref
          ??const?ref?=?{current:?initialValue};
          ??hook.memoizedState?=?ref;
          ??return?ref;
          }

          function?updateRef<T>(initialValue:?T):?{|current:?T|}?{
          ??//?獲取當(dāng)前useRef?hook
          ??const?hook?=?updateWorkInProgressHook();
          ??//?返回保存的數(shù)據(jù)
          ??return?hook.memoizedState;
          }

          可以看到,ref對(duì)象確實(shí)僅僅是包含current屬性的對(duì)象。

          function ref

          除了{current: any}類型外,ref還能作為function

          作為function時(shí),僅僅是在不同生命周期階段被調(diào)用的回調(diào)函數(shù)。

          在我們接下來的討論中,只涉及function | {current: any}這兩種ref數(shù)據(jù)結(jié)構(gòu)

          ref的生命周期

          React中,HostComponent、ClassComponentForwardRef可以賦值ref屬性。

          這個(gè)屬性在ref生命周期的不同階段會(huì)被執(zhí)行(對(duì)于function)或賦值(對(duì)于{current: any})。

          //?HostComponent
          div>
          //?ClassComponent?/?ForwardRef
          <App?ref={cpnRef}?/>

          其中,ForwardRef只是將ref作為第二個(gè)參數(shù)傳遞下去,沒有別的特殊處理。

          //?對(duì)于ForwardRef,secondArg為傳遞下去的ref
          const?children?=?forwardRef(
          ??(props,?secondArg)?=>?{
          ????//render邏輯...
          ??}
          );

          所以接下來討論ref生命周期時(shí)不會(huì)單獨(dú)討論ForwardRef。

          在本系列文章中我們講過,React的渲染包含兩個(gè)階段:

          • render階段:為需要更新的組件對(duì)應(yīng)fiber打上標(biāo)簽(effectTag

          • commit階段:執(zhí)行effectTag對(duì)應(yīng)更新操作

          //?部分effectTag定義
          //?插入DOM
          export?const?Placement?=?/*?*/?0b0000000000000010;
          //?更新DOM的屬性
          export?const?Update?=?/*????*/?0b0000000000000100;
          //?刪除DOM
          export?const?Deletion?=?/*??*/?0b0000000000001000;
          //?有ref操作
          export?const?Ref?=?/*???????*/?0b0000000010000000;
          //?...

          對(duì)于HostComponent、ClassComponent如果包含ref操作,那么也會(huì)賦值相應(yīng)的effectTag。

          同其他effectTag對(duì)應(yīng)操作的執(zhí)行一樣,ref的更新也是發(fā)生在commit階段。

          所以,ref生命周期可以分為兩個(gè)大階段:

          • render階段為含有ref屬性的Component對(duì)應(yīng)fiber添加Ref effectTag

          • commit階段為包含Ref effectTagfiber執(zhí)行對(duì)應(yīng)操作

          render階段

          render階段組件對(duì)應(yīng)fiber被賦值Ref effectTag需要滿足的條件:

          • fiber類型為HostComponent、ClassComponentScopeComponent

          ScopeComponent是一種用于管理focus的測(cè)試特性,這種情況我們不討論。詳見PR[3]

          • 對(duì)于mountworkInProgress.ref !== null,即組件首次render時(shí)存在ref屬性

          • 對(duì)于updatecurrent.ref !== workInProgress.ref,即組件更新時(shí)ref屬性改變

          commit階段

          commit階段ref生命周期分為兩個(gè)子階段:

          • 移除之前的ref

          • 更新ref

          移除之前的ref

          • 對(duì)于ref屬性改變的情況,需要先移除之前的ref

          調(diào)用的是commitDetachRef

          function?commitDetachRef(current:?Fiber)?{
          ??const?currentRef?=?current.ref;
          ??if?(currentRef?!==?null)?{
          ????if?(typeof?currentRef?===?'function')?{
          ??????//?function類型ref,調(diào)用他,傳參為null
          ??????currentRef(null);
          ????}?else?{
          ??????//?對(duì)象類型ref,current賦值為null
          ??????currentRef.current?=?null;
          ????}
          ??}
          }

          可以看到,function{current: any}類型的ref生命周期并沒有什么不同,只是一種會(huì)被調(diào)用,一種會(huì)被賦值。

          • 對(duì)于Deletion effectTagfiber(對(duì)應(yīng)需要?jiǎng)h除的DOM節(jié)點(diǎn)),需要遞歸他的子樹,對(duì)子孫fiberref執(zhí)行類似commitDetachRef的操作。

          更新ref

          接下來進(jìn)入ref的更新階段。

          執(zhí)行這一步的操作叫commitAttachRef

          function?commitAttachRef(finishedWork:?Fiber)?{
          ??//?finishedWork為含有Ref?effectTag的fiber
          ??const?ref?=?finishedWork.ref;
          ??
          ??//?含有ref?prop,這里是作為數(shù)據(jù)結(jié)構(gòu)
          ??if?(ref?!==?null)?{
          ????//?獲取ref屬性對(duì)應(yīng)的Component實(shí)例
          ????const?instance?=?finishedWork.stateNode;
          ????let?instanceToUse;
          ????switch?(finishedWork.tag)?{
          ??????case?HostComponent:
          ????????//?對(duì)于HostComponent,實(shí)例為對(duì)應(yīng)DOM節(jié)點(diǎn)
          ????????instanceToUse?=?getPublicInstance(instance);
          ????????break;
          ??????default:
          ????????//?其他類型實(shí)例為fiber.stateNode
          ????????instanceToUse?=?instance;
          ????}

          ????//?賦值ref
          ????if?(typeof?ref?===?'function')?{
          ??????ref(instanceToUse);
          ????}?else?{
          ??????ref.current?=?instanceToUse;
          ????}
          ??}
          }

          可以看到,對(duì)于包含ref屬性的fiber,針對(duì)ref的不同類型,執(zhí)行調(diào)用/賦值操作。

          至此,ref生命周期完成。

          總結(jié)

          通過本文我們學(xué)習(xí)了ref數(shù)據(jù)結(jié)構(gòu)生命周期。

          對(duì)于賦值了ref屬性的HostComponentClassComponent,他會(huì)依次經(jīng)歷:

          • render階段賦值Ref effectTag

          • 如果ref變化,在commit階段會(huì)先刪除之前的ref。

          • 接下來,會(huì)進(jìn)入ref的更新流程。

          所以,對(duì)于內(nèi)聯(lián)函數(shù)ref

          ?this.dom?=?dom}>div>

          由于每次render ref都對(duì)應(yīng)一個(gè)全新的內(nèi)聯(lián)函數(shù),所以在commit階段會(huì)先執(zhí)行commitDetachRef刪除再執(zhí)行commitAttachRef更新。

          內(nèi)聯(lián)函數(shù)會(huì)被調(diào)用兩次,第一次傳參dom的值為null,第二次為更新的DOM。

          參考資料

          [1]

          #1373: https://github.com/facebook/react/issues/1373

          [2]

          #8333: https://github.com/facebook/react/pull/8333#issuecomment-271648615

          [3]

          PR: https://github.com/facebook/react/pull/16587




          推薦閱讀




          我的公眾號(hào)能帶來什么價(jià)值?(文末有送書規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐国产在线播放 | 91激情在线 | 国产黄在线观看 | 欧美日韩亚州在线观看 | 67194亚洲 |