精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

React ref 從原理到應用

開發 前端
Fiber是React更新時的最小單元,是一種包含指針的數據結構,從數據結構上看Fiber架構 ≈ 樹 + 鏈表。Fiber單元是從 jsx createElement之后根據ReactElement生成的,相比 ReactElement,Fiber單元具備動態工作能力。

[[398682]]

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

正如官網所介紹的這樣:

 

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

前置知識

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

Fiber架構

Fiber是React更新時的最小單元,是一種包含指針的數據結構,從數據結構上看Fiber架構 ≈ 樹 + 鏈表。

Fiber單元是從 jsx createElement之后根據ReactElement生成的,相比 ReactElement,Fiber單元具備動態工作能力。

React 的工作流程

使用chrome perfomance錄制一個react應用渲染看函數調用棧會看到下面這張圖

這三塊內容分別代表: 1.生成react root節點 2.reconciler 協調生成需要更新的子節點 3.將節點更新commit 到視圖

Hooks基礎知識

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

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

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

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

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

useImperativeHandle 和 useRef也包括在內。

調試源碼

本文已梳理摘取了源碼相關的函數,但你如果配合源碼調試一起食用效果會更加。

本文基于React v17.0.2。

拉取React代碼并安裝依賴

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

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

3.進入build/node_modules/react/cjs 執行yarn link 同理 react-dom

4.在 build/node_modules/react/cjs/react.development.js中加入link標記console以確保檢查link狀態

5.使用create-react-app創建一個測試應用 并link react,react-dom

ref prop

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

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

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

我們知道React的解析是從createElement開始的,找到了下面創建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.   ); 

從createElement開始就已經創建了對ref屬性的引用。

createElement之后我們需要構建Fiber工作樹,接下來主要講對ref相關的處理。

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

先主要關注 FunctionComponent/ClassComponent/HostComponent(原生html標簽)

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. functin renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes){ 
  10.             children = Component(props, secondArg); // 這里的Component就是指我們的函數組件 
  11.                 return children; 

我們可以看到函數組件在渲染的時候就是直接執行。

Class組件和原生標簽的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. function constructClassInstance(workInProgress, ctor, props) { 
  12.     .... 
  13.   var instance = new ctor(props, context); 
  14.   // 把instance實例掛載到workInProgress stateNode屬性上 
  15.   adoptClassInstance(workInProgress, instance); 
  16.     ..... 
  17.   return instance; 
  18. function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) { 
  19.   // 標記是否有ref更新 
  20.   markRef(current, workInProgress); 
  21. function markRef(current, workInProgress) { 
  22.   var ref = workInProgress.ref; 
  23.   if (current === null && ref !== null || current !== null && current.ref !== ref) { 
  24.     // Schedule a Ref effect 
  25.     workInProgress.flags |= Ref; 
  26.   } 

ClassComponent則是通過構造函數生成實例并標記了ref屬性。

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

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

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

函數組件的ref prop

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

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

forwardRef

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

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

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

  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.         // 第三個參數type就是forwardRef創建的elementType 
  20.         return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes); 
  21.       } 
  22.    
  23. function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) { 
  24.     .... 
  25.   var render = Component.render; 
  26.   var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent 
  27.   var nextChildren; 
  28.   { 
  29.         ... 
  30.     //  將ref引用傳入renderWithHooks 
  31.     nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes); 
  32.     ... 
  33.   } 
  34.   workInProgress.flags |= PerformedWork; 
  35.   reconcileChildren(current, workInProgress, nextChildren, renderLanes); 
  36.   return workInProgress.child; 

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

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

用上面的例子驗證一下

  1. const Parent = () => {   
  2.   const childRef = useRef(null
  3.   useEffect(()=>{ 
  4.     console.log(childref) // { current:null } 
  5.   }) 
  6.   return <Child ref={childRef}/> 
  7. const Child = forwardRef((props,ref) => { 
  8.     return <div>Child</div> 
  9. }} 
  10.                           
  11.  const Parent = () => {  
  12.   const childRef = useRef(null
  13.   useEffect(()=>{ 
  14.     console.log(childref) // { current: div } 
  15.   }) 
  16.   return <Child ref={childRef}/> 
  17. const Child = forwardRef((props,ref) => { 
  18.     return <div ref={ref}>Child</div> 
  19. }} 

 結合輸出可以看出如果單獨使用forwardRef僅僅只能轉發ref屬性。如果ref最終沒有綁定到一個ClassCompnent或者原生DOM上那么這個ref將不會改變。

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

useImperativeHandle

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

先看下效果

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

 看一下該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(nullcreate, ref), effectDeps); 
  16. function imperativeHandleEffect(create, ref) { 
  17.   if (typeof ref === 'function') { 
  18.     var refCallback = ref; 
  19.     var _inst = create(); 
  20.     refCallback(_inst); 
  21.     return function () { 
  22.       refCallback(null); 
  23.     }; 
  24.   } else if (ref !== null && ref !== undefined) { 
  25.     var refObject = ref; 
  26.     { 
  27.       if (!refObject.hasOwnProperty('current')) { 
  28.         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(', ') + '}'); 
  29.       } 
  30.     } 
  31.         // 這里執行了傳給hook的第二個參數 
  32.     var _inst2 = create(); 
  33.     refObject.current = _inst2; 
  34.     return function () { 
  35.       refObject.current = null
  36.     }; 
  37.   } 

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

同一份引用

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

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

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

  1. const isEqualRefDemo = () => { 
  2.     const isEqualRef = useRef(1) 
  3.   return <input key="test" ref={isEqualRef}> 

對于 class component 和 原生標簽來說 就是 createElement 到 commitAttachRef之前:

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

對于函數組件來說就是 createElement 到 hook執行 imperativeHandleEffect 之前:

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

 

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

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

小節總結

1.ref出現在組件上時是一個保留屬性

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

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

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

創建ref的方式

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

字符串ref & callback ref

這兩種創建ref的方式不再贅述,官網以及社區優秀文章可供參考。

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 <input ref={this.myRef} /> 
  13.   } 

為什么不能在函數組件中使用createRef

結合第一節的內容以及 createRef的源碼,我們發現,這不過就是在類組件內部掛載了一個可變對象。因為類組件構造函數不會被反復執行,因此這個createRef自然保持同一份引用。但是到了函數組件就不一樣了,每一次組件更新, 因為沒有特殊處理createRef會被反復重新創建執行,因此在函數組件中使用createRef將不能達到只有同一份引用的效果。

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

 useRef

React 16.8中出現了hooks,使得我們可以在函數組件中定義狀態,同時也帶來了 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. function updateRef(initialValue) { 
  11.   var hook = updateWorkInProgressHook(); 
  12.   return hook.memoizedState; 

借助hook數據結構,第一次useRef時將創建的值保存在memoizedState中,之后每次更新階段則直接返回。

這樣在函數組件更新時重復執行useRef仍返回同一份引用。

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

被擴展的ref

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

useRef的應用

解決閉包問題

由于函數組件每次執行形成的閉包,下面這段代碼會始終打印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 <div>{ count }</div> 

 將 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 <div>{ count }</div> 

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

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

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

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

使用useRef創建引用

  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的邏輯和觸發更新的邏輯解耦了 
  8.           if(countRef.current < 5){ 
  9.             countRef.current++ 
  10.           } else { 
  11.             setCount(countRef.current
  12.           } 
  13.         }, 1000) 
  14.         return () => clearInterval(interval) 
  15.       }, []) 
  16.     return <div>{ count }</div> 

 封裝自定義hooks

useCreation

通過factory函數來避免類似于 useRef(new Construcotr)中構造函數的重復執行

  1. import { useRef } from 'react'
  2. export default function useCreation<T>(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. function depsAreSame(oldDeps: any[], deps: any[]): boolean { 
  15.   if (oldDeps === deps) return true
  16.   for (const i in oldDeps) { 
  17.     if (oldDeps[i] !== deps[i]) return false
  18.   } 
  19.   return true

usePrevious

通過創建兩個ref來保存前一次的state

  1. import { useRef } from 'react'
  2. export type compareFunction<T> = (prev: T | undefined, next: T) => boolean; 
  3. function usePrevious<T>(state: T, compare?: compareFunction<T>): T | undefined { 
  4.   const prevRef = useRef<T>(); 
  5.   const curRef = useRef<T>(); 
  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. export default usePrevious; 

useClickAway

自定義的元素失焦響應hook

  1. import { useEffect, useRef } from 'react'
  2. export type BasicTarget<T = HTMLElement> = 
  3.   | (() => T | null
  4.   | T 
  5.   | null 
  6.   | MutableRefObject<T | null | undefined>; 
  7.    
  8.  export function getTargetElement( 
  9.   target?: BasicTarget<TargetElement>, 
  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. // 鼠標點擊事件,click 不會監聽右鍵 
  25. const defaultEvent = 'click'
  26. type EventType = MouseEvent | TouchEvent; 
  27. export default function useClickAway( 
  28.   onClickAway: (event: EventType) => void, 
  29.   target: BasicTarget | BasicTarget[], 
  30.   eventName: string = defaultEvent, 
  31. ) { 
  32.   // 使用useRef保存回調函數 
  33.   const onClickAwayRef = useRef(onClickAway); 
  34.   onClickAwayRef.current = onClickAway; 
  35.   useEffect(() => { 
  36.     const handler = (event: any) => { 
  37.       const targets = Array.isArray(target) ? target : [target]; 
  38.       if ( 
  39.         targets.some((targetItem) => { 
  40.           const targetElement = getTargetElement(targetItem) as HTMLElement; 
  41.           return !targetElement || targetElement?.contains(event.target); 
  42.         }) 
  43. ) { 
  44.         return
  45.       } 
  46.       onClickAwayRef.current(event); 
  47.     }; 
  48.     document.addEventListener(eventName, handler); 
  49.     return () => { 
  50.       document.removeEventListener(eventName, handler); 
  51.     }; 
  52.   }, [target, eventName]); 

以上自定義hooks均出自ahooks

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

參考資料

  • React Fiber https://juejin.cn/post/6844903975112671239#heading-10
  • React 官網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

 

責任編輯:姜華 來源: Eval Studio
相關推薦

2025-04-02 07:29:14

2018-05-17 15:18:48

Logistic回歸算法機器學習

2024-07-07 21:49:22

2025-11-13 08:08:15

2024-03-27 10:14:48

2010-06-29 14:20:52

2025-10-27 01:22:00

HTTP接口API

2025-11-07 04:00:00

2025-05-13 08:25:00

模塊化編程JavaScript

2020-04-28 22:12:30

Nginx正向代理反向代理

2022-02-28 10:05:12

組件化架構設計從原組件化模塊化

2023-08-03 08:03:05

2025-04-03 00:03:00

數據內存網絡

2025-11-11 07:54:21

2025-09-05 07:13:13

2023-06-15 10:53:57

2019-11-23 17:27:54

IO開源

2025-05-28 08:45:00

2025-10-30 07:45:06

2025-04-07 03:02:00

電腦內存數據
點贊
收藏

51CTO技術棧公眾號

欧美一级鲁丝片| 人妻少妇一区二区三区| 亚洲一级淫片| 精品国产成人系列| 亚洲免费av一区二区三区| 免费在线观看黄色网| 成人午夜在线播放| 国产精品99久久久久久久久| 91插插插插插插| 伦理一区二区三区| 欧美日韩国产高清一区二区三区 | 97精品视频| 欧美va在线播放| 天天色综合社区| 绿色成人影院| 一区二区在线观看视频在线观看| 久久精品一二三区| 国产乱色精品成人免费视频| 亚洲久久一区二区| 日韩最新av在线| 亚洲av成人无码一二三在线观看| 福利一区二区三区视频在线观看| 亚洲国产精品久久不卡毛片| 亚洲三级一区| 日韩偷拍自拍| 成人妖精视频yjsp地址| 国产精品亚洲激情| 国产免费av一区| 欧美日韩国产色综合一二三四| 国产一区二区三区欧美| 久久久久麻豆v国产精华液好用吗| 日韩大陆av| 色综合久久综合网欧美综合网| 亚洲五码在线观看视频| 黄色网页在线播放| 中文字幕欧美三区| 欧美中日韩一区二区三区| 女人18毛片一区二区三区| 老司机午夜精品| 国产成人精品在线视频| 国产原创视频在线| 99亚洲伊人久久精品影院红桃| 美女精品视频一区| 一区二区国产精品精华液| 欧美日韩国产传媒| 一区二区三区无码高清视频| 日本高清www| 欧美三级电影在线| 亚洲福利小视频| 乱码一区二区三区| 亚洲第一二区| 日韩视频一区在线观看| www激情五月| 日本免费精品| 亚洲精品在线电影| 国产麻豆剧传媒精品国产av| 激情小说一区| 日韩精品在线免费播放| 三级黄色片网站| 性欧美lx╳lx╳| 亚洲人成免费电影| 少妇av片在线观看| 日韩成人精品一区二区| 日韩在线播放视频| 国产又粗又硬又长又爽| 在线观看国产精品入口| 欧美黑人巨大xxx极品| 久久久全国免费视频| 欧美日韩午夜| 91国内在线视频| 波多野结衣高清在线| 日本欧美大码aⅴ在线播放| 国产精品视频地址| 国产一区二区三区黄片| 国产精品一区二区三区99| 国产精品国产精品国产专区蜜臀ah | 欧美剧在线免费观看网站 | 日韩亚洲一区二区| 欧美三级小视频| 日韩五码在线| 国产精品国产福利国产秒拍| 国产精品亚洲lv粉色| 粉嫩aⅴ一区二区三区四区五区 | 欧美成人激情| 欧美久久精品一级黑人c片| 国产一级片视频| 久久亚洲一区| 国产主播精品在线| 免费观看黄一级视频| 337p粉嫩大胆色噜噜噜噜亚洲| 日韩欧美一区二区在线观看 | 人妻无码中文字幕| 日本一区二区在线不卡| 男人天堂新网址| 欧美亚洲韩国| 日韩欧美另类在线| 黄免费在线观看| 欧美三级在线| 国产精品盗摄久久久| 高清乱码毛片入口| 国产精品视频免费看| av日韩一区二区三区| 国产91在线精品| 亚洲国产精品久久久久秋霞不卡| 快灬快灬一下爽蜜桃在线观看| 欧美涩涩网站| 国产日韩视频在线观看| 亚洲精品一区二区三区新线路| 久久久精品国产免费观看同学| 好色先生视频污| 成人激情综合| 亚洲精品一区二区三区影院| 免费黄色国产视频| 天堂久久久久va久久久久| 成人免费视频网站| 色多多视频在线观看| 91国模大尺度私拍在线视频| 波多野吉衣在线视频| 91影院成人| 51ⅴ精品国产91久久久久久| 99久久99久久久精品棕色圆| 国产精品乱人伦| 午夜免费精品视频| 欧美一区二区三区红桃小说| 久久99久久99精品免观看粉嫩| 在线观看国产黄| 国产偷国产偷亚洲高清人白洁| 青青青免费在线| 日韩中文字幕在线一区| 久热99视频在线观看| 最近中文字幕在线观看视频| 26uuu亚洲| 精品少妇人妻av免费久久洗澡| 天堂久久av| 久久天堂电影网| 97精品人妻一区二区三区| 中文字幕 久热精品 视频在线 | 国产精品久久久久久久久免费高清 | 日韩在线欧美| 国产精品一区二区女厕厕| 欧美视频免费一区二区三区| 欧美日韩美女在线观看| 亚洲中文字幕无码一区| 韩国一区二区三区在线观看| 91精品国产一区二区三区动漫| 粗大黑人巨茎大战欧美成人| 69久久夜色精品国产69蝌蚪网| 亚洲色图 激情小说| 奇米在线7777在线精品| 亚洲国产欧美一区二区三区不卡| 人人鲁人人莫人人爱精品| 亚洲视频专区在线| 人人妻人人爽人人澡人人精品| 国产三级欧美三级| 香港日本韩国三级网站| 国产精品久久久久蜜臀| 91亚洲国产成人精品性色| 超碰在线网址| 精品成人免费观看| 亚洲免费黄色网址| 久久久久久久久久久黄色| 9久久婷婷国产综合精品性色| 欧美一区二区三区激情视频| 国产精品偷伦视频免费观看国产| 秋霞午夜在线观看| 欧美电影免费观看完整版| 中文字幕第28页| 久久毛片高清国产| 浓精h攵女乱爱av| 影音先锋日韩精品| 国产综合av一区二区三区| 欧美日韩精品免费观看视完整| 丝袜亚洲另类欧美重口| 亚洲欧美黄色片| 黑人欧美xxxx| 久久精品在线观看视频| 国产精品一区在线观看你懂的| www.av片| 日韩精品首页| 国产伦精品一区二区三区视频黑人| 欧美成人a交片免费看| 日韩中文字幕视频在线| 亚洲成人一级片| 色av成人天堂桃色av| 小早川怜子一区二区的演员表| 国产91精品免费| 久草精品在线播放| 欧美黄免费看| 日韩.欧美.亚洲| 精品一区二区三区中文字幕视频| 午夜精品一区二区三区av| a天堂在线资源| 日韩精品一区二区三区在线播放| 中文字幕av影院| 亚洲欧美日韩国产一区二区三区| 呦呦视频在线观看| 极品少妇一区二区三区精品视频 | 日本中文不卡| 一区二区网站| 国产精品在线看| 碰碰在线视频| 久久99热这里只有精品国产| 国产精品视频二区三区| 精品国产免费一区二区三区香蕉| 国产精品免费无遮挡无码永久视频| 一区二区三区欧美视频| 国产精品天天干| 成人精品视频.| 久久久久久久久久久久久久久国产| 亚洲经典三级| 在线观看17c| 日本欧美国产| 欧美精品v日韩精品v国产精品| 精品网站999| 国产精品久久久久久亚洲调教| 999av小视频在线| 欧美成人激情视频| av一区在线观看| 亚洲男人的天堂在线| 亚洲精品人妻无码| 91精品国产乱码| 中文在线资源天堂| 色综合欧美在线视频区| 国产精品黄色大片| 亚洲成a人v欧美综合天堂| 特一级黄色录像| 国产精品短视频| 娇妻被老王脔到高潮失禁视频| 91麻豆精东视频| 特级西西人体wwwww| 丁香六月久久综合狠狠色| 日韩av影视大全| 国内精品在线播放| 91小视频在线播放| 九九**精品视频免费播放| 波多野结衣天堂| 日韩影院精彩在线| 老司机午夜av| 久久久www| 日日摸天天爽天天爽视频| 一区二区三区福利| 久久精品视频16| aa亚洲婷婷| 逼特逼视频在线| 欧美亚洲一级| 成人在线看视频| 日韩电影在线免费看| 美女黄色片视频| 美女高潮久久久| 999久久久精品视频| 国产呦精品一区二区三区网站| 欧美视频亚洲图片| 国产成人99久久亚洲综合精品| xxxx视频在线观看| 白白色 亚洲乱淫| 中文乱码人妻一区二区三区视频| 99久久亚洲一区二区三区青草| 中文字幕在线观看网址| 久久久久久久av麻豆果冻| 男女做爰猛烈刺激| 国产精品美日韩| 国产一区二区视频在线观看免费| 亚洲精品国产视频| 国产福利拍拍拍| 欧美亚洲动漫另类| 国产特级aaaaaa大片| 精品久久久久一区| 天堂在线免费av| 日韩在线播放av| 91福利区在线观看| 国产极品精品在线观看| 亚洲精品伦理| 豆国产97在线| 国产区精品区| 97精品国产97久久久久久粉红| 欧美日韩专区| 成人精品视频一区二区| 黄页网站大全一区二区| 亚洲自拍偷拍精品| 亚洲国产精品av| 妺妺窝人体色www在线下载| 激情av一区二区| 亚洲天堂中文网| 亚洲成人激情图| 国产区在线视频| 色综合91久久精品中文字幕| 成人性生活视频| 亚洲一区中文字幕| 日韩丝袜视频| 青青草综合视频| 日韩精品免费专区| 四虎永久免费观看| 欧美国产日韩亚洲一区| 精品无码m3u8在线观看| 欧美日韩在线免费视频| 丰满熟女一区二区三区| 中文字幕一区日韩电影| 国产三级伦理在线| 国产中文字幕日韩| 亚洲三级性片| 国产美女作爱全过程免费视频| 日韩和欧美的一区| 中文文字幕文字幕高清| 亚洲乱码中文字幕| 国产污视频网站| 亚洲国产美女精品久久久久∴| 午夜在线视频播放| 清纯唯美亚洲综合| 136导航精品福利| 亚洲在线不卡| 首页亚洲欧美制服丝腿| 三级视频网站在线观看| 亚洲欧美国产高清| 亚洲自拍第二页| 亚洲人高潮女人毛茸茸| 国产不卡人人| 51蜜桃传媒精品一区二区| 久久亚洲成人| 日本a√在线观看| 91麻豆精东视频| 日本在线免费观看| 精品成人在线观看| 性xxxxfjsxxxxx欧美| 91亚洲精品一区| 97久久视频| 伊人影院综合在线| 国产日产精品一区| 无码人妻精品一区二区50| 日韩精品欧美激情| 蜜桃av在线播放| 久久精彩视频| 国产精品综合色区在线观看| 在线观看免费视频国产| 一区二区三区四区不卡在线| aaa一区二区三区| 大胆欧美人体视频| 欧美日本三级| 日产精品久久久久久久蜜臀| 国产精品中文字幕日韩精品 | 国产小视频免费在线网址| 69视频在线播放| 亚洲深夜福利在线观看| 北条麻妃69av| 久久美女艺术照精彩视频福利播放| 久久艹免费视频| 亚洲色图av在线| 国产 日韩 欧美一区| 日日骚一区二区网站| 美日韩一区二区| 卡通动漫亚洲综合| 日韩欧美二区三区| 黄色在线观看视频网站| 国产伦精品一区| 亚洲综合不卡| 国产在线免费av| 日韩视频在线一区二区| 俄罗斯一级**毛片在线播放| 国产一区在线免费| 久久精品亚洲一区二区| 日本午夜精品视频| 欧美丰满高潮xxxx喷水动漫| 午夜av在线播放| 国产一区二区三区av在线 | 91视频福利网| 亚洲午夜免费视频| 免费在线国产| 成人疯狂猛交xxx| 欧美激情综合| 国产成人无码一区二区在线观看| 在线观看国产精品网站| 欧美成人xxx| 精品久久久久久综合日本| 免费在线播放第一区高清av| 极品蜜桃臀肥臀-x88av| 欧美一区二区三区成人| 韩日毛片在线观看| 亚洲免费视频一区| 丰满放荡岳乱妇91ww| 国产精品va无码一区二区三区| 在线观看中文字幕亚洲| 日韩激情精品| 韩国日本美国免费毛片| 亚洲精品高清在线| 蜜桃视频在线免费| 97欧洲一区二区精品免费| 亚洲综合精品| 999福利视频| 日韩精品免费在线观看| 欧美爱爱视频| 国产成人a亚洲精v品无码| 中文字幕日韩欧美一区二区三区| 日韩一区免费视频| 国产欧美一区二区三区久久 | 亚洲精品第一国产综合野| 日本天堂影院在线视频| 亚洲最大成人免费视频| 肉丝袜脚交视频一区二区| 欧美日韩成人免费观看| 原创国产精品91| 久久1电影院|