1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 5881字,需瀏覽 12分鐘

         ·

        2022-06-28 23:18

        作者:卡頌

        介:《React技術揭秘》作者

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


        大家好,我卡頌。


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


        顯然,正常航行時是不需要逃生艙的,只有在遇到危險時會用到。


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


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


        今天,我們來聊聊Ref的使用場景。


        為什么是逃生艙?



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


        這是因為二者操作的都是脫離React控制的因素


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


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


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


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


        失控的Ref



        對于Ref,什么叫失控呢?


        首先來看不失控的情況:


        • 執(zhí)行ref.currentfocus、blur等方法

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

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


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

        但是下面的情況:

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

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


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

        舉個例子,下面是React文檔中的例子:

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

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

        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é)點。

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

        如果這兩種移除P節(jié)點的方式混用,那么先點擊按鈕1再點擊按鈕2就會報錯:


        這就是使用Ref操作DOM造成的失控情況導致的。

        如何限制失控



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

        React中,組件可以分為:

        • 高階組件

        • 低階組件


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

        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失控的范圍控制在單個組件內(nèi),不會出現(xiàn)跨越組件的ref失控。

        文檔中的示例為例,如果我們想在Form組件中點擊按鈕,操作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>
            </>
          );
        }

        點擊后,會報錯:


        這是因為在Form組件中向MyInput傳遞ref失敗了,inputRef.current并沒有指向input節(jié)點。

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

        人為取消限制



        如果一定要取消這個限制,可以使用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)生關聯(lián)。

        在實踐中,一些同學可能覺得forwardRef這一API有些多此一舉。

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

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

        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ù)結構:

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

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

        總結



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

        為了防止錯用/濫用導致ref失控,React限制默認情況下,不能跨組件傳遞ref。

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

        為了減少refDOM的濫用,可以使用useImperativeHandle限制ref傳遞的數(shù)據(jù)結構。



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

        - END -


        瀏覽 48
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            乌克兰AV片 | 亚欧精品久久久久久久久久久 | wwwxxxx国产 | 哺乳期丰满乳亲伦小说 | JUy黑人喝羽月希奶水 | 肏屄网站 | 成人毛片18女人毛片免费按摩店 | 亚洲无码免费网站 | 国产三男一女4p免费男黑人 | 激情丁香婷婷五月天 |