<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>

          React新文檔:不要濫用Ref哦~

          共 5881字,需瀏覽 12分鐘

           ·

          2022-06-28 23:18

          作者:卡頌

          簡(jiǎn)介:《React技術(shù)揭秘》作者

          來源:SegmentFault  思否社區(qū) 


          大家好,我卡頌。


          React新文檔有個(gè)很有意思的細(xì)節(jié):useRefuseEffect這兩個(gè)API的介紹,在文檔中所在的章節(jié)叫Escape Hatches(逃生艙)。


          顯然,正常航行時(shí)是不需要逃生艙的,只有在遇到危險(xiǎn)時(shí)會(huì)用到。


          如果開發(fā)者過多依賴這兩個(gè)API,可能是誤用。


          React新文檔:不要濫用effect哦中我們談到useEffect的正確使用場(chǎng)景。


          今天,我們來聊聊Ref的使用場(chǎng)景。


          為什么是逃生艙?



          先思考一個(gè)問題:為什么refeffect被歸類到逃生艙中?


          這是因?yàn)槎卟僮鞯亩际?strong>脫離React控制的因素。


          effect中處理的是副作用。比如:在useEffect中修改了document.title


          document.title不屬于React中的狀態(tài),React無法感知他的變化,所以被歸類到effect中。


          同樣,使DOM聚焦需要調(diào)用element.focus(),直接執(zhí)行DOM API也是不受React控制的。


          雖然他們是脫離React控制的因素,但為了保證應(yīng)用的健壯,React也要盡可能防止他們失控。


          失控的Ref



          對(duì)于Ref,什么叫失控呢?


          首先來看不失控的情況:


          • 執(zhí)行ref.currentfocusblur等方法

          • 執(zhí)行ref.current.scrollIntoView使element滾動(dòng)到視野內(nèi)

          • 執(zhí)行ref.current.getBoundingClientRect測(cè)量DOM尺寸


          這些情況下,雖然我們操作了DOM,但涉及的都是React控制范圍外的因素,所以不算失控。

          但是下面的情況:

          • 執(zhí)行ref.current.remove移除DOM

          • 執(zhí)行ref.current.appendChild插入子節(jié)點(diǎn)


          同樣是操作DOM,但這些屬于React控制范圍內(nèi)的因素,通過ref執(zhí)行這些操作就屬于失控的情況。

          舉個(gè)例子,下面是React文檔中的例子:

          文檔地址:
          https://codesandbox.io/s/sandpack-project-forked-s33q3c

          按鈕1點(diǎn)擊后會(huì)插入/移除 P節(jié)點(diǎn),按鈕2點(diǎn)擊后會(huì)調(diào)用DOM API移除P節(jié)點(diǎn):

          export default function Counter() {  const [show, setShow] = useState(true);  const ref = useRef(null);  return (    <div>
                <button
                  onClick={() => {
                    setShow(!show);
                  }}>
                  Toggle with setState      </button>
                <button
                  onClick={() => {
                    ref.current.remove();
                  }}>
                  Remove from the DOM      </button>
                {show && <p ref={ref}>Hello world</p>}    </div>
            );
          }

          按鈕1通過React控制的方式移除P節(jié)點(diǎn)。

          按鈕2直接操作DOM移除P節(jié)點(diǎn)。

          如果這兩種移除P節(jié)點(diǎn)的方式混用,那么先點(diǎn)擊按鈕1再點(diǎn)擊按鈕2就會(huì)報(bào)錯(cuò):


          這就是使用Ref操作DOM造成的失控情況導(dǎo)致的。

          如何限制失控



          現(xiàn)在問題來了,既然叫失控了,那就是React沒法控制的(React總不能限制開發(fā)者不能使用DOM API吧?),那如何限制失控呢?

          React中,組件可以分為:

          • 高階組件

          • 低階組件


          低階組件指那些基于DOM封裝的組件,比如下面的組件,直接基于input節(jié)點(diǎn)封裝:

          function MyInput(props) {  return <input {...props} />;
          }

          低階組件中,是可以直接將ref指向DOM的,比如:

          function MyInput(props) {  const ref = useRef(null);  return <input ref={ref} {...props} />;
          }

          高階組件指那些基于低階組件封裝的組件,比如下面的Form組件,基于Input組件封裝:

          function Form() {  return (    <>
                <MyInput/>
              </>
            )
          }

          高階組件無法直接將ref指向DOM,這一限制就將ref失控的范圍控制在單個(gè)組件內(nèi),不會(huì)出現(xiàn)跨越組件的ref失控

          文檔中的示例為例,如果我們想在Form組件中點(diǎn)擊按鈕,操作input聚焦:

          文檔地址:
          https://codesandbox.io/s/sandpack-project-forked-7zqgmd

          function MyInput(props) {  return <input {...props} />;
          }function Form() {  const inputRef = useRef(null);  function handleClick() {
              inputRef.current.focus();
            }  return (    <>
                <MyInput ref={inputRef} />
                <button onClick={handleClick}>
                  input聚焦      </button>
              </>
            );
          }

          點(diǎn)擊后,會(huì)報(bào)錯(cuò):


          這是因?yàn)樵?/span>Form組件中向MyInput傳遞ref失敗了,inputRef.current并沒有指向input節(jié)點(diǎn)。

          究其原因,就是上面說的為了將ref失控的范圍控制在單個(gè)組件內(nèi),React默認(rèn)情況下不支持跨組件傳遞ref

          人為取消限制



          如果一定要取消這個(gè)限制,可以使用forwardRef API顯式傳遞ref

          const MyInput = forwardRef((props, ref) => {  return <input {...props} ref={ref} />;
          });function Form() {  const inputRef = useRef(null);  function handleClick() {
              inputRef.current.focus();
            }  return (    <>
                <MyInput ref={inputRef} />
                <button onClick={handleClick}>
                  Focus the input      </button>
              </>
            );
          }

          使用forwardRefforward在這里是傳遞的意思)后,就能跨組件傳遞ref

          在例子中,我們將inputRefForm跨組件傳遞到MyInput中,并與input產(chǎn)生關(guān)聯(lián)。

          在實(shí)踐中,一些同學(xué)可能覺得forwardRef這一API有些多此一舉。

          但從ref失控的角度看,forwardRef的意圖就很明顯了:既然開發(fā)者手動(dòng)調(diào)用forwardRef破除防止ref失控的限制,那他應(yīng)該知道自己在做什么,也應(yīng)該自己承擔(dān)相應(yīng)的風(fēng)險(xiǎn)。

          同時(shí),有了forwardRef的存在,發(fā)生ref相關(guān)錯(cuò)誤后也更容易定位錯(cuò)誤。

          useImperativeHandle



          除了限制跨組件傳遞ref外,還有一種防止ref失控的措施,那就是useImperativeHandle,他的邏輯是這樣的:

          既然ref失控是由于使用了不該被使用的DOM方法(比如appendChild),那我可以限制ref中只存在可以被使用的方法

          useImperativeHandle修改我們的MyInput組件:

          const MyInput = forwardRef((props, ref) => {  const realInputRef = useRef(null);  useImperativeHandle(ref, () => ({    focus() {
                realInputRef.current.focus();
              },
            }));  return <input {...props} ref={realInputRef} />;
          });

          現(xiàn)在,Form組件中通過inputRef.current只能取到如下數(shù)據(jù)結(jié)構(gòu):

          {  focus() {
              realInputRef.current.focus();
            },
          }

          就杜絕了開發(fā)者通過ref取到DOM后,執(zhí)行不該被使用的API,出現(xiàn)ref失控的情況。

          總結(jié)



          正常情況,Ref的使用比較少,他是作為逃生艙而存在的。

          為了防止錯(cuò)用/濫用導(dǎo)致ref失控React限制默認(rèn)情況下,不能跨組件傳遞ref

          為了破除這種限制,可以使用forwardRef

          為了減少ref對(duì)DOM的濫用,可以使用useImperativeHandle限制ref傳遞的數(shù)據(jù)結(jié)構(gòu)。



          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 48
          點(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>
                  日日碰狠狠添天天爽 | 一区无码 | 一级无码爱爱片免费 | 欧美成人手机版 | 伊人久久综合 |