日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Reactref從原理到應(yīng)用

提到 ref或者 refs 如果你用過React 16以前的版本 第一印象都是用來訪問DOM或者修改組件實例的,

正如官網(wǎng)所介紹的這樣:

然后到了React 16.3出現(xiàn)的 createRef 以及16.8 hooks中的 useRef出現(xiàn)時,發(fā)現(xiàn)這里的ref好像不僅僅只有之前的綁定到DOM/組件實例的 作用?本文將帶你逐一梳理這些知識點,并嘗試分析相關(guān)源碼。

前置知識

這部分知識點不是本文重點,每個點展開都非常龐大,了方便本文理解先在這里簡單提及。

Fiber架構(gòu)

Fiber是React更新時的最小單元,是一種包含指針的數(shù)據(jù)結(jié)構(gòu),從數(shù)據(jù)結(jié)構(gòu)上看Fiber架構(gòu) ≈ 樹 + 鏈表。

Fiber單元是從 jsx createElement之后根據(jù)ReactElement生成的,相比 ReactElement,F(xiàn)iber單元具備動態(tài)工作能力。

React 的工作流程

使用chrome perfomance錄制一個react應(yīng)用渲染看函數(shù)調(diào)用棧會看到下面這張圖

這三塊內(nèi)容分別代表: 1.生成react root節(jié)點 2.reconciler 協(xié)調(diào)生成需要更新的子節(jié)點 3.將節(jié)點更新commit 到視圖

Hooks基礎(chǔ)知識

在函數(shù)組件中每執(zhí)行一次use開頭的hook函數(shù)都會生成一個hook對象。

 
 
 
 
  1. type Hook = {
  2.   memoizedState: any,   // 上次更新之后的最終狀態(tài)值
  3.   queue: UpdateQueue, //更新隊列
  4.   next, // 下一個 hook 對象
  5. };

其中memoizedState會保存該hook上次更新之后的最終狀態(tài),比如當(dāng)我們使用一次useState之后就會在memoizedState中保存初始值。

React 中大部分 hook 分為兩個階段:第一次初始化時`mount`階段和更新`update`時階段

hooks函數(shù)的執(zhí)行分兩個階段 mount和 update,比如 useState只會在初始化時執(zhí)行一次,下文中將提到的

useImperativeHandle 和 useRef也包括在內(nèi)。

調(diào)試源碼

本文已梳理摘取了源碼相關(guān)的函數(shù),但你如果配合源碼調(diào)試一起食用效果會更加。

本文基于React v17.0.2。

拉取React代碼并安裝依賴

將react,scheduler以及react-dom打包為commonjs

yarn build react/index,react-dom/index,scheduler --type NODE

3.進(jìn)入build/node_modules/react/cjs 執(zhí)行yarn link 同理 react-dom

4.在 build/node_modules/react/cjs/react.development.js中加入link標(biāo)記console以確保檢查link狀態(tài)

5.使用create-react-app創(chuàng)建一個測試應(yīng)用 并link react,react-dom

ref prop

組件上的ref屬性是一個保留屬性,你不能把ref當(dāng)成一個普通的prop屬性在一個組件中獲取,比如:

 
 
 
 
  1. const Parent = () => {
  2.     return 
  3. }
  4. const Child = (props) => {
  5.   console.log(props);
  6.   // 這里獲取不到ref屬性
  7.     return 
  8. }

 這個ref去哪里了呢, React本身又對它做了什么呢?

我們知道React的解析是從createElement開始的,找到了下面創(chuàng)建ReactElement的地方,確實有對ref保留屬性的處理。

 
 
 
 
  1. export function createElement(type, config, children) {
  2. let propName;
  3.   // Reserved names are extracted
  4.   const props = {};
  5.   let ref = null;
  6.   if (config != null) {
  7.     if (hasValidRef(config)) {
  8.       ref = config.ref;
  9.     }
  10.     for (propName in config) {
  11.       if (
  12.         hasOwnProperty.call(config, propName) &&
  13.         !RESERVED_PROPS.hasOwnProperty(propName)
  14.       ) {
  15.         props[propName] = config[propName];
  16.       }
  17.     }
  18.   }
  19.   return ReactElement(
  20.     type,
  21.     key,
  22.     ref,
  23.     props,
  24.     ...
  25.   );
  26. }

從createElement開始就已經(jīng)創(chuàng)建了對ref屬性的引用。

createElement之后我們需要構(gòu)建Fiber工作樹,接下來主要講對ref相關(guān)的處理。

React對于不同的組件有不通的處理

先主要關(guān)注 FunctionComponent/ClassComponent/HostComponent(原生html標(biāo)簽)

FunctionComponent

 
 
 
 
  1. function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
  2.       try {
  3.         nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderLanes);
  4.       } finally {
  5.         reenableLogs();
  6.       }
  7.       reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  8.       return workInProgress.child;
  9. }
  10. functin renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes){
  11.             children = Component(props, secondArg); // 這里的Component就是指我們的函數(shù)組件
  12.                 return children;
  13. }

我們可以看到函數(shù)組件在渲染的時候就是直接執(zhí)行。

Class組件和原生標(biāo)簽的ref prop

ClassComponent

 
 
 
 
  1. function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
  2.   ...
  3.   {
  4.     ...
  5.     constructClassInstance(workInProgress, Component, nextProps);
  6.         ....
  7.   }
  8.   var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes);
  9.     ...
  10.   return nextUnitOfWork;
  11. }
  12. function constructClassInstance(workInProgress, ctor, props) {
  13.     ....
  14.   var instance = new ctor(props, context);
  15.   // 把instance實例掛載到workInProgress stateNode屬性上
  16.   adoptClassInstance(workInProgress, instance);
  17.     .....
  18.   return instance;
  19. }
  20. function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
  21.   // 標(biāo)記是否有ref更新
  22.   markRef(current, workInProgress);
  23. }
  24. function markRef(current, workInProgress) {
  25.   var ref = workInProgress.ref;
  26.   if (current === null && ref !== null || current !== null && current.ref !== ref) {
  27.     // Schedule a Ref effect
  28.     workInProgress.flags |= Ref;
  29.   }
  30. }

ClassComponent則是通過構(gòu)造函數(shù)生成實例并標(biāo)記了ref屬性。

回顧一下之前提到的React工作流程,既然是要將組件實例或者真實DOM賦值給ref那肯定不能在一開始就處理這個ref,而是根據(jù)標(biāo)記到commit階段再給ref賦值。

 
 
 
 
  1. function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
  2.     ....
  3.   {
  4.     if (finishedWork.flags & Ref) {
  5.       commitAttachRef(finishedWork);
  6.     }
  7.   }
  8.   ....
  9. }
  10. function commitAttachRef(finishedWork) {
  11.   var ref = finishedWork.ref;
  12.   if (ref !== null) {
  13.     var instance = finishedWork.stateNode;
  14.     var instanceToUse;
  15.     switch (finishedWork.tag) {
  16.       case HostComponent:
  17.         // getPublicInstance 這里調(diào)用了DOM API 返回了DOM對象
  18.         instanceToUse = getPublicInstance(instance);
  19.         break;
  20.       default:
  21.         instanceToUse = instance;
  22.     } 
  23.     // 對函數(shù)回調(diào)形式設(shè)置ref的處理
  24.     if (typeof ref === 'function') {
  25.       {
  26.         ref(instanceToUse);
  27.       }
  28.     } else {
  29.       ref.current = instanceToUse;
  30.     }
  31.   }
  32. }

在commit階段,如果是原生標(biāo)簽則將真實DOM賦值給ref對象的current屬性, 如果是class componnet 則是組件instance。

函數(shù)組件的ref prop

如果你對function組件未做處理直接加上ref,react會直接忽略并在開發(fā)環(huán)境給出警告

函數(shù)組件沒有實例可以賦值給ref對象,而且組件上的ref prop會被當(dāng)作保留屬性無法在組件中獲取,那該怎么辦呢?

forwardRef

React提供了一個forwardRef函數(shù) 來處理函數(shù)組件的 ref prop,用起來就像下面這個示例:

 
 
 
 
  1. const Parent = () => {
  2.     const childRef = useRef(null)
  3.   return 
  4. }
  5. const Child = forWardRef((props,ref) => {
  6.     return 
    Child
  7. }}

 這個方法的源碼主體也非常簡單,返回了一個新的elementType對象,這個對象的render屬性包含了原本的這個函數(shù)組件,而$$typeof則標(biāo)記了這個特殊組件類型。

 
 
 
 
  1. function forwardRef(render) {
  2.   ....
  3.   var elementType = {
  4.     $$typeof: REACT_FORWARD_REF_TYPE,
  5.     render: render
  6.   }
  7.   ....
  8.   return elementType;
  9.  }

那么React對forwardRef這個特殊的組件是怎么處理的呢

 
 
 
 
  1. function beginWork(current, workInProgress, renderLanes) {
  2.     ...
  3.   switch (workInProgress.tag) {
  4.     case FunctionComponent:
  5.       {
  6.        ...
  7.         return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
  8.       }
  9.     case ClassComponent:
  10.       {
  11.                 ....
  12.         return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
  13.       }
  14.     case HostComponent:
  15.       return updateHostComponent(current, workInProgress, renderLanes);
  16.     case ForwardRef:
  17.       {
  18.                 ....
  19.         // 第三個參數(shù)type就是forwardRef創(chuàng)建的elementType
  20.         return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes);
  21.       }
  22. }
  23.   
  24. function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) {
  25.     ....
  26.   var render = Component.render;
  27.   var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent
  28.   var nextChildren;
  29.   {
  30.         ...
  31.     //  將ref引用傳入renderWithHooks
  32.     nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes);
  33.     ...
  34.   }
  35.   workInProgress.flags |= PerformedWork;
  36.   reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  37.   return workInProgress.child;
  38. }

可以看到和上面 FunctionComponent的主要區(qū)別僅僅是把ref保留屬性當(dāng)成普通屬性傳入 renderWithHooks方法!

那么又有一個問題出現(xiàn)了,如果只是傳了一個ref引用,而沒有像Class組件那樣可以attach的實例,豈不是沒有辦法操作子函數(shù)組件的行為?

用上面的例子驗證一下

 
 
 
 
  1. const Parent = () => {  
  2.   const childRef = useRef(null)
  3.   useEffect(()=>{
  4.     console.log(childref) // { current:null }
  5.   })
  6.   return 
  7. }
  8. const Child = forwardRef((props,ref) => {
  9.     return 
    Child
  10. }}
  11.                          
  12.  const Parent = () => { 
  13.   const childRef = useRef(null)
  14.   useEffect(()=>{
  15.     console.log(childref) // { current: div }
  16.   })
  17.   return 
  18. }
  19. const Child = forwardRef((props,ref) => {
  20.     return Child
  • }}
  •  結(jié)合輸出可以看出如果單獨使用forwardRef僅僅只能轉(zhuǎn)發(fā)ref屬性。如果ref最終沒有綁定到一個ClassCompnent或者原生DOM上那么這個ref將不會改變。

    假設(shè)一個業(yè)務(wù)場景,你封裝了一個表單組件,想對外暴露一些接口比如說提交的action以及校驗等操作,這樣應(yīng)該如何處理呢?

    useImperativeHandle

    react為我們提供了這個hook來幫助函數(shù)組件向外部暴露屬性

    先看下效果

     
     
     
     
    1. const Parent = () => {  
    2.   const childRef = useRef(null)
    3.   useEffect(()=>{
    4.     chilRef.current.sayName();// child
    5.   })
    6.   return 
    7. }
    8. const Child = forwardRef((props,ref) => {
    9.   useImperativeHandle(ref,()=>({
    10.     sayName:()=>{
    11.         console.log('child')
    12.     }
    13.   }))
    14.     return 
      Child
    15. }}

     看一下該hook的源碼部分(以hook mount階段為例):

     
     
     
     
    1. useImperativeHandle: function (ref, create, deps) {
    2.       currentHookNameInDev = 'useImperativeHandle';
    3.       mountHookTypesDev();
    4.       checkDepsAreArrayDev(deps);
    5.       return mountImperativeHandle(ref, create, deps);
    6.  }
    7. function mountImperativeHandle(ref, create, deps) {
    8.   {
    9.     if (typeof create !== 'function') {
    10.       error('Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: %s.', create !== null ? typeof create : 'null');
    11.     }
    12.   } // TODO: If deps are provided, should we skip comparing the ref itself?
    13.   var effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null;
    14.   var fiberFlags = Update;
    15.   return mountEffectImpl(fiberFlags, Layout, imperativeHandleEffect.bind(null, create, ref), effectDeps);
    16. }
    17. function imperativeHandleEffect(create, ref) {
    18.   if (typeof ref === 'function') {
    19.     var refCallback = ref;
    20.     var _inst = create();
    21.     refCallback(_inst);
    22.     return function () {
    23.       refCallback(null);
    24.     };
    25.   } else if (ref !== null && ref !== undefined) {
    26.     var refObject = ref;
    27.     {
    28.       if (!refObject.hasOwnProperty('current')) {
    29.         error('Expected useImperativeHandle() first argument to either be a ' + 'ref callback or React.createRef() object. Instead received: %s.', 'an object with keys {' + Object.keys(refObject).join(', ') + '}');
    30.       }
    31.     }
    32.         // 這里執(zhí)行了傳給hook的第二個參數(shù)
    33.     var _inst2 = create();
    34.     refObject.current = _inst2;
    35.     return function () {
    36.       refObject.current = null;
    37.     };
    38.   }
    39. }

    其實就是將我們需要暴露的對象及傳給useImperativeHandle的第二個函數(shù)參數(shù)執(zhí)行結(jié)果賦值給了ref的current對象。

    同一份引用

    到此為止我們大致梳理了組件上ref prop 的工作流程,以及如何在函數(shù)組件中使用ref prop,貌似比想象中簡單。

    上面的過程我們注意到從createElement再到構(gòu)建WorkInProgess Fiber樹到最后commit的過程,ref似乎是一直在被傳遞。

    中間過程的代碼過于龐大復(fù)雜,但是我們可以通過一個簡單的測試來驗證一下。

     
     
     
     
    1. const isEqualRefDemo = () => {
    2.     const isEqualRef = useRef(1)
    3.   return 
    4. }

    對于 class component 和 原生標(biāo)簽來說 就是 createElement 到 commitAttachRef之前:

    在createElement里將ref掛載給window對象,然后在commitAttachRef里判斷一下這兩次的ref是否全等。

    對于函數(shù)組件來說就是 createElement 到 hook執(zhí)行 imperativeHandleEffect 之前:

     
     
     
     
    1. const Parent = () => {  
    2.   const childRef = useRef(1)
    3.   useEffect(()=>{
    4.     chilRef.current.sayName();// child
    5.   })
    6.   return 
    7. }
    8. const Child = forwardRef((props,ref) => {
    9.   useImperativeHandle(ref,()=>({
    10.     sayName:()=>{
    11.         console.log('child')
    12.     }
    13.   }))
    14.     return 
      Child
    15. }}

    從createElement添加ref到React整個渲染過程的末尾(commit階段)被賦值前,這個ref都是同一份引用。

    這也正如 ref單詞的本意 reference引用一樣。

    小節(jié)總結(jié)

    1.ref出現(xiàn)在組件上時是一個保留屬性

    2.ref在組件存在的生命周期內(nèi)維護(hù)了同一個引用(可變對象 MutableObject)

    3.當(dāng)ref掛載的對象是原生html標(biāo)簽時會ref對象的current屬性會被賦值為真實DOM 而如果是React組件會被賦值為React"組件實例"

    4.ref掛載都在commit階段處理

    創(chuàng)建ref的方式

    ref prop相當(dāng)于在組件上挖了一個“坑” 來承接 ref對象,但是這樣還不夠我們還需要先創(chuàng)建ref對象

    字符串ref & callback ref

    這兩種創(chuàng)建ref的方式不再贅述,官網(wǎng)以及社區(qū)優(yōu)秀文章可供參考。

    https://zh-hans.reactjs.org/docs/refs-and-the-dom.html

    https://blog.logrocket.com/how-to-use-react-createref-ea014ad09dba/

    createRef & useRef

    createRef

    16.3引入了createRef這個api

    createRef的源碼就是一個閉包,對外暴露了 一個具有 current屬性的對象。

    我們一般會這樣在class component中使用createRef

     
     
     
     
    1. class CreateRefComponent extends React.Component {
    2.   constructor(props) {
    3.     super(props);
    4.     this.myRef = React.createRef()
    5.   }
    6.   componentDidMount() {
    7.     this.myRef.current.focus()
    8.     console.log(this.myRef.current)
    9.     // dom input
    10.   }
    11.   render() {
    12.     return 
    13.   }
    14. }

    為什么不能在函數(shù)組件中使用createRef

    結(jié)合第一節(jié)的內(nèi)容以及 createRef的源碼,我們發(fā)現(xiàn),這不過就是在類組件內(nèi)部掛載了一個可變對象。因為類組件構(gòu)造函數(shù)不會被反復(fù)執(zhí)行,因此這個createRef自然保持同一份引用。但是到了函數(shù)組件就不一樣了,每一次組件更新, 因為沒有特殊處理createRef會被反復(fù)重新創(chuàng)建執(zhí)行,因此在函數(shù)組件中使用createRef將不能達(dá)到只有同一份引用的效果。

     
     
     
     
    1. const CreateRefInFC = () => {
    2.   const valRef = React.createRef();  // 如果在函數(shù)組件中使用createRef 在這個例子中點擊后ref就會被重新創(chuàng)建因此將始終顯示為null
    3.   const [, update] = React.useState();
    4.   return 
    5.     value: {valRef.current}
    6.      {
    7.       valRef.current = 80;
    8.       update({});
    9.     }}>+
    10.     
    11.   
  • }
  •  useRef

    React 16.8中出現(xiàn)了hooks,使得我們可以在函數(shù)組件中定義狀態(tài),同時也帶來了 useRef

    再來看moutRef和updateRef所做的事:

     
     
     
     
    1. function mountRef(initialValue) {
    2.   var hook = mountWorkInProgressHook();
    3.   {
    4.     var _ref2 = {
    5.       current: initialValue
    6.     };
    7.     hook.memoizedState = _ref2;
    8.     return _ref2;
    9.   }
    10. }
    11. function updateRef(initialValue) {
    12.   var hook = updateWorkInProgressHook();
    13.   return hook.memoizedState;
    14. }

    借助hook數(shù)據(jù)結(jié)構(gòu),第一次useRef時將創(chuàng)建的值保存在memoizedState中,之后每次更新階段則直接返回。

    這樣在函數(shù)組件更新時重復(fù)執(zhí)行useRef仍返回同一份引用。

    因此實際上和 createRef一樣本質(zhì)上只是創(chuàng)建了一個 Mutable Object,只是因為渲染方式的不同,在函數(shù)組件中做了一些處理。而掛載和卸載的行為全部交由組件本身來維護(hù)。

    被擴展的ref

    從 createRef開始我們可以看到,ref對象的消費不再和DOM以及組件屬性所綁定了,這意味著你可以在任何地方消費他們,這也回答了本文一開始的那個問題。

    useRef的應(yīng)用

    解決閉包問題

    由于函數(shù)組件每次執(zhí)行形成的閉包,下面這段代碼會始終打印1

     
     
     
     
    1. export const ClosureDemo =  () => {
    2.     const [ count,setCount ] = useState(0);
    3.     useEffect(()=> {
    4.         const interval = setInterval(()=>{
    5.           setCount(count+1)
    6.         }, 1000)
    7.         return () => clearInterval(interval)
    8.       }, [])
    9.     // count顯示始終是1
    10.     return 
      { count }
    11. }

     將 count 作為依賴傳入useEffect可以解決上面這個問題

     
     
     
     
    1. export const ClosureDemo =  () => {
    2.     const [ count,setCount ] = useState(0);
    3.     useEffect(()=> {
    4.         const interval = setInterval(()=>{
    5.           setCount(count+1)
    6.         }, 1000)
    7.         return () => clearInterval(interval)
    8.       }, [count])
    9.     return 
      { count }
    10. }

     但是這樣定時器也會隨著count值的更新而被不斷創(chuàng)建,一方面會帶來性能問題(這個例子中沒有那么明顯),更重要的一個方面是它不符合我們的開發(fā)語義,因為很明顯我們希望定時器本身是不變的。

    另外一個方式也可以處理這個問題

     
     
     
     
    1. export const ClosureDemo =  () => {
    2.     const [ count,setCount ] = useState(0);
    3.     useEffect(()=> {
    4.         const interval = setInterval(()=>{
    5.           setCount(count=> count + 1) // 使用setSate函數(shù)式更新可以確保每次都取到新的值
    6.         }, 1000)
    7.         return () => clearInterval(interval)
    8.       }, [])
    9.     return 
      { count }
    10. }

     這樣做確實可以處理閉包帶來的影響,但是僅限于需要使用setState的場景,對數(shù)據(jù)的修改和觸發(fā)setState是需要綁定的,這可能會造成不必要的刷新。

    使用useRef創(chuàng)建引用

     
     
     
     
    1. export const ClosureDemo =  () => {
    2.     const [ count,setCount ] = useState(0);
    3.     const countRef = useRef(0);
    4.     countRef.current = count
    5.     useEffect(()=> {
    6.         const interval = setInterval(()=>{
    7.           // 這里將更新count的邏輯和觸發(fā)更新的邏輯解耦了
    8.           if(countRef.current < 5){
    9.             countRef.current++
    10.           } else {
    11.             setCount(countRef.current)
    12.           }
    13.         }, 1000)
    14.         return () => clearInterval(interval)
    15.       }, [])
    16.     return 
      { count }
    17. }

     封裝自定義hooks

    useCreation

    通過factory函數(shù)來避免類似于 useRef(new Construcotr)中構(gòu)造函數(shù)的重復(fù)執(zhí)行

     
     
     
     
    1. import { useRef } from 'react';
    2. export default function useCreation(factory: () => T, deps: any[]) {
    3.   const { current } = useRef({
    4.     deps,
    5.     obj: undefined as undefined | T,
    6.     initialized: false,
    7.   });
    8.   if (current.initialized === false || !depsAreSame(current.deps, deps)) {
    9.     current.deps = deps;
    10.     current.obj = factory();
    11.     current.initialized = true;
    12.   }
    13.   return current.obj as T;
    14. }
    15. function depsAreSame(oldDeps: any[], deps: any[]): boolean {
    16.   if (oldDeps === deps) return true;
    17.   for (const i in oldDeps) {
    18.     if (oldDeps[i] !== deps[i]) return false;
    19.   }
    20.   return true;
    21. }

    usePrevious

    通過創(chuàng)建兩個ref來保存前一次的state

     
     
     
     
    1. import { useRef } from 'react';
    2. export type compareFunction = (prev: T | undefined, next: T) => boolean;
    3. function usePrevious(state: T, compare?: compareFunction): T | undefined {
    4.   const prevRef = useRef();
    5.   const curRef = useRef();
    6.   const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;
    7.   if (needUpdate) {
    8.     prevRef.current = curRef.current;
    9.     curRef.current = state;
    10.   }
    11.   return prevRef.current;
    12. }
    13. export default usePrevious;

    useClickAway

    自定義的元素失焦響應(yīng)hook

     
     
     
     
    1. import { useEffect, useRef } from 'react';
    2. export type BasicTarget =
    3.   | (() => T | null)
    4.   | T
    5.   | null
    6.   | MutableRefObject;
    7.   
    8.  export function getTargetElement(
    9.   target?: BasicTarget,
    10.   defaultElement?: TargetElement,
    11. ): TargetElement | undefined | null {
    12.   if (!target) {
    13.     return defaultElement;
    14.   }
    15.   let targetElement: TargetElement | undefined | null;
    16.   if (typeof target === 'function') {
    17.     targetElement = target();
    18.   } else if ('current' in target) {
    19.     targetElement = target.current;
    20.   } else {
    21.     targetElement = target;
    22.   }
    23.   return targetElement;
    24. }
    25. // 鼠標(biāo)點擊事件,click 不會監(jiān)聽右鍵
    26. const defaultEvent = 'click';
    27. type EventType = MouseEvent | TouchEvent;
    28. export default function useClickAway(
    29.   onClickAway: (event: EventType) => void,
    30.   target: BasicTarget | BasicTarget[],
    31.   eventName: string = defaultEvent,
    32. ) {
    33.   // 使用useRef保存回調(diào)函數(shù)
    34.   const onClickAwayRef = useRef(onClickAway);
    35.   onClickAwayRef.current = onClickAway;
    36.   useEffect(() => {
    37.     const handler = (event: any) => {
    38.       const targets = Array.isArray(target) ? target : [target];
    39.       if (
    40.         targets.some((targetItem) => {
    41.           const targetElement = getTargetElement(targetItem) as HTMLElement;
    42.           return !targetElement || targetElement?.contains(event.target);
    43.         })
    44. ) {
    45.         return;
    46.       }
    47.       onClickAwayRef.current(event);
    48.     };
    49.     document.addEventListener(eventName, handler);
    50.     return () => {
    51.       document.removeEventListener(eventName, handler);
    52.     };
    53.   }, [target, eventName]);
    54. }

    以上自定義hooks均出自ahooks

    還有許多好用的自定義hook以及倉庫比如react-use都基于useRef自定義了很多好用的hook。

    參考資料

    • React Fiber https://juejin.cn/post/6844903975112671239#heading-10
    • React 官網(wǎng)ref使用 https://zh-hans.reactjs.org/docs/refs-and-the-dom.html#gatsby-focus-wrapper
    • React 前生今世 https://zhuanlan.zhihu.com/p/40462264
    • React ref源碼分析 https://blog.csdn.net/qq_32281471/article/details/98473846

    網(wǎng)站標(biāo)題:Reactref從原理到應(yīng)用
    網(wǎng)站URL:http://www.5511xx.com/article/cddpogi.html