<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 進(jìn)階」 React 全部 Hooks 使用大全 (包含 React v18 版本 )

          共 33654字,需瀏覽 68分鐘

           ·

          2022-07-16 02:55

          React Hooks.png

          一 前言

          React hooks是react16.8 以后,react新增的鉤子API,目的是增加代碼的可復(fù)用性,邏輯性,彌補(bǔ)無(wú)狀態(tài)組件沒(méi)有生命周期,沒(méi)有數(shù)據(jù)管理狀態(tài)state的缺陷。本章節(jié)筆者將介紹目前 React 提供的所有 hooks ,介紹其功能類(lèi)型和基本使用方法。

          創(chuàng)作不易,希望屏幕前的你能給筆者賞個(gè),以此鼓勵(lì)我繼續(xù)創(chuàng)作前端硬文。??????

          1.1 技術(shù)背景

          react hooks 解決了什么問(wèn)題?

          先設(shè)想一下,如果沒(méi)有 Hooks,函數(shù)組件能夠做的只是接受 Props、渲染 UI ,以及觸發(fā)父組件傳過(guò)來(lái)的事件。所有的處理邏輯都要在類(lèi)組件中寫(xiě),這樣會(huì)使 class 類(lèi)組件內(nèi)部錯(cuò)綜復(fù)雜,每一個(gè)類(lèi)組件都有一套獨(dú)特的狀態(tài),相互之間不能復(fù)用,即便是 React 之前出現(xiàn)過(guò) mixin 等復(fù)用方式,但是伴隨出 mixin 模式下隱式依賴(lài),代碼沖突覆蓋等問(wèn)題,也不能成為 React 的中流砥柱的邏輯復(fù)用方案。所以 React 放棄 mixin 這種方式。

          類(lèi)組件是一種面向?qū)ο笏枷氲捏w現(xiàn),類(lèi)組件之間的狀態(tài)會(huì)隨著功能增強(qiáng)而變得越來(lái)越臃腫,代碼維護(hù)成本也比較高,而且不利于后期 tree shaking。所以有必要做出一套函數(shù)組件代替類(lèi)組件的方案,于是 Hooks 也就理所當(dāng)然的誕生了。

          所以 Hooks 出現(xiàn)本質(zhì)上原因是:

          • 讓函數(shù)組件也能做類(lèi)組件的事,有自己的狀態(tài),可以處理一些副作用,能獲取 ref ,也能做數(shù)據(jù)緩存。
          • 解決邏輯復(fù)用難的問(wèn)題。
          • 放棄面向?qū)ο缶幊?,擁抱函?shù)式編程。

          為什么要使用自定義 Hooks ?

          自定義 hooks 是在 React Hooks 基礎(chǔ)上的一個(gè)拓展,可以根據(jù)業(yè)務(wù)需求制定滿(mǎn)足業(yè)務(wù)需要的組合 hooks ,更注重的是邏輯單元。通過(guò)業(yè)務(wù)場(chǎng)景不同,到底需要React Hooks 做什么,怎么樣把一段邏輯封裝起來(lái),做到復(fù)用,這是自定義 hooks 產(chǎn)生的初衷。

          自定義 hooks 也可以說(shuō)是 React Hooks 聚合產(chǎn)物,其內(nèi)部有一個(gè)或者多個(gè) React Hooks 組成,用于解決一些復(fù)雜邏輯。

          1.2 技術(shù)愿景

          目前 hooks 已經(jīng)成為 React 主流的開(kāi)發(fā)手段,React 生態(tài)也日益朝著 hooks 方向發(fā)展,比如 React Router, React Redux 等, hooks 也更契合 React 生態(tài)庫(kù)的應(yīng)用。

          隨著項(xiàng)目功能模塊越來(lái)越復(fù)雜,一些公共邏輯不能有效的復(fù)用,這些邏輯需要和業(yè)務(wù)代碼強(qiáng)關(guān)聯(lián)到一起,這樣會(huì)讓整體工程臃腫,功能不能復(fù)用,如果涉及到修改邏輯,那么有可能牽一發(fā)動(dòng)全身。

          所以有必要使用自定義 hooks 的方式,hooks 可以把重復(fù)的邏輯抽離出去,根據(jù)需要?jiǎng)?chuàng)建和業(yè)務(wù)功能綁定的業(yè)務(wù)型 hooks ,或者是根據(jù)具體功能創(chuàng)建的功能型 hooks 。

          1.3 功能概覽

          在 React 的世界中,不同的 hooks 使命也是不同的,我這里對(duì) React hooks 按照功能分類(lèi),分成了 數(shù)據(jù)更新驅(qū)動(dòng),狀態(tài)獲取與傳遞,執(zhí)行副作用,狀態(tài)派生與保存,和工具類(lèi)型, 具體功能劃分和使用場(chǎng)景如下:

          WechatIMG32.png

          二 hooks 之?dāng)?shù)據(jù)更新驅(qū)動(dòng)

          2.1 useState

          useState 可以使函數(shù)組件像類(lèi)組件一樣擁有 state,函數(shù)組件通過(guò) useState 可以讓組件重新渲染,更新視圖。

          useState 基礎(chǔ)介紹:

          const [ ①state , ②dispatch ] = useState(③initData)

          ① state,目的提供給 UI ,作為渲染視圖的數(shù)據(jù)源。

          ② dispatchAction 改變 state 的函數(shù),可以理解為推動(dòng)函數(shù)組件渲染的渲染函數(shù)。

          ③ initData 有兩種情況,第一種情況是非函數(shù),將作為 state 初始化的值。第二種情況是函數(shù),函數(shù)的返回值作為 useState 初始化的值。

          useState 基礎(chǔ)用法:

          const DemoState = (props) => {
             /* number為此時(shí)state讀取值 ,setNumber為派發(fā)更新的函數(shù) */
             let [number, setNumber] = useState(0/* 0為初始值 */
             return (<div>
                 <span>{ number }</span>
                 <button onClick={ ()=> {
                   setNumber(number+1)
                   console.log(number) /* 這里的number是不能夠即使改變的  */
                 } } ></button>
             </div>
          )
          }

          useState 注意事項(xiàng):

          ① 在函數(shù)組件一次執(zhí)行上下文中,state 的值是固定不變的。

          function Index(){
              const [ number, setNumber ] = React.useState(0)
              const handleClick = () => setInterval(()=>{
                  // 此時(shí) number 一直都是 0
                  setNumber(number + 1 ) 
              },1000)
              return <button onClick={ handleClick } > 點(diǎn)擊 { number }</button>
          }

          ② 如果兩次 dispatchAction 傳入相同的 state 值,那么組件就不會(huì)更新。

          export default function Index(){
              const [ state  , dispatchState ] = useState({ name:'alien' })
              const  handleClick = ()=>// 點(diǎn)擊按鈕,視圖沒(méi)有更新。
                  state.name = 'Alien'
                  dispatchState(state) // 直接改變 `state`,在內(nèi)存中指向的地址相同。
              }
              return <div>
                   <span> { state.name }</span>
                  <button onClick={ handleClick }  >changeName++</button>
              </div>

          }

          ③ 當(dāng)觸發(fā) dispatchAction 在當(dāng)前執(zhí)行上下文中獲取不到最新的 state, 只有再下一次組件 rerender 中才能獲取到。

          2.2 useReducer

          useReducer 是 react-hooks 提供的能夠在無(wú)狀態(tài)組件中運(yùn)行的類(lèi)似redux的功能 api 。

          useReducer 基礎(chǔ)介紹:

          const [ ①state , ②dispatch ] = useReducer(③reducer)

          ① 更新之后的 state 值。

          ② 派發(fā)更新的 dispatchAction 函數(shù), 本質(zhì)上和 useState 的 dispatchAction 是一樣的。

          ③ 一個(gè)函數(shù) reducer ,我們可以認(rèn)為它就是一個(gè) redux 中的 reducer , reducer的參數(shù)就是常規(guī)reducer里面的state和action, 返回改變后的state, 這里有一個(gè)需要注意的點(diǎn)就是:如果返回的 state 和之前的 state ,內(nèi)存指向相同,那么組件將不會(huì)更新。

          useReducer 基礎(chǔ)用法:

          const DemoUseReducer = ()=>{
              /* number為更新后的state值,  dispatchNumbner 為當(dāng)前的派發(fā)函數(shù) */
             const [ number , dispatchNumbner ] = useReducer((state,action)=>{
                 const { payload , name  } = action
                 /* return的值為新的state */
                 switch(name){
                     case 'add':
                         return state + 1
                     case 'sub':
                         return state - 1 
                     case 'reset':
                       return payload       
                 }
                 return state
             },0)
             return <div>
                當(dāng)前值:{ number }
                { /* 派發(fā)更新 */ }
                <button onClick={()=>dispatchNumbner({ name:'add' })} >增加</button>
                <button onClick={()=>dispatchNumbner({ name:'sub' })} >減少</button>
                <button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >賦值</button>
                { /* 把dispatch 和 state 傳遞給子組件  */ }
                <MyChildren  dispatch={ dispatchNumbner } State={{ number }} />
             </div>

          }

          2.3 useSyncExternalStore

          useSyncExternalStore 的誕生并非偶然,和 v18 的更新模式下外部數(shù)據(jù)的 tearing 有著十分緊密的關(guān)聯(lián)。useSyncExternalStore 能夠讓 React 組件在 concurrent 模式下安全地有效地讀取外接數(shù)據(jù)源,在組件渲染過(guò)程中能夠檢測(cè)到變化,并且在數(shù)據(jù)源發(fā)生變化的時(shí)候,能夠調(diào)度更新。當(dāng)讀取到外部狀態(tài)發(fā)生了變化,會(huì)觸發(fā)一個(gè)強(qiáng)制更新,來(lái)保證結(jié)果的一致性。

          useSyncExternalStore 基礎(chǔ)介紹:

          useSyncExternalStore(
              subscribe,
              getSnapshot,
              getServerSnapshot
          )

          ① subscribe 為訂閱函數(shù),當(dāng)數(shù)據(jù)改變的時(shí)候,會(huì)觸發(fā) subscribe,在 useSyncExternalStore 會(huì)通過(guò)帶有記憶性的 getSnapshot 來(lái)判別數(shù)據(jù)是否發(fā)生變化,如果發(fā)生變化,那么會(huì)強(qiáng)制更新數(shù)據(jù)。

          ② getSnapshot 可以理解成一個(gè)帶有記憶功能的選擇器。當(dāng) store 變化的時(shí)候,會(huì)通過(guò) getSnapshot 生成新的狀態(tài)值,這個(gè)狀態(tài)值可提供給組件作為數(shù)據(jù)源使用,getSnapshot 可以檢查訂閱的值是否改變,改變的話(huà)那么會(huì)觸發(fā)更新。

          ③ getServerSnapshot 用于 hydration 模式下的 getSnapshot。

          useSyncExternalStore 基礎(chǔ)用法:

          import { combineReducers , createStore  } from 'redux'

          /* number Reducer */
          function numberReducer(state=1,action){
              switch (action.type){
                case 'ADD':
                  return state + 1
                case 'DEL':
                  return state - 1
                default:
                  return state
              }
          }

          /* 注冊(cè)reducer */
          const rootReducer = combineReducers({ number:numberReducer  })
          /* 創(chuàng)建 store */
          const store = createStore(rootReducer,{ number:1  })

          function Index(){
              /* 訂閱外部數(shù)據(jù)源 */
              const state = useSyncExternalStore(store.subscribe,() => store.getState().number)
              console.log(state)
              return <div>
                  {state}
                  <button onClick={() => store.dispatch({ type:'ADD' })} >點(diǎn)擊</button>
              </div>

          }

          點(diǎn)擊按鈕,會(huì)觸發(fā) reducer ,然后會(huì)觸發(fā) store.subscribe 訂閱函數(shù),執(zhí)行 getSnapshot 得到新的 number ,判斷 number 是否發(fā)生變化,如果變化,觸發(fā)更新。

          2.4 useTransition

          在 React v18 中,有一種新概念叫做過(guò)渡任務(wù),這種任務(wù)是對(duì)比立即更新任務(wù)而產(chǎn)生的,通常一些影響用戶(hù)交互直觀響應(yīng)的任務(wù),例如按鍵,點(diǎn)擊,輸入等,這些任務(wù)需要視圖上立即響應(yīng),所以可以稱(chēng)之為立即更新的任務(wù),但是有一些更新不是那么急迫,比如頁(yè)面從一個(gè)狀態(tài)過(guò)渡到另外一個(gè)狀態(tài),這些任務(wù)就叫做過(guò)渡任務(wù)。打個(gè)比方如下圖當(dāng)點(diǎn)擊 tab 從 tab1 切換到 tab2 的時(shí)候,本質(zhì)上產(chǎn)生了兩個(gè)更新任務(wù)。

          • 第一個(gè)就是 hover 狀態(tài)由 tab1 變成 tab2。

          • 第二個(gè)就是內(nèi)容區(qū)域由 tab1 內(nèi)容變換到 tab2 內(nèi)容。

          這兩個(gè)任務(wù),用戶(hù)肯定希望 hover 狀態(tài)的響應(yīng)更迅速,而內(nèi)容的響應(yīng)有可能還需要請(qǐng)求數(shù)據(jù)等操作,所以更新?tīng)顟B(tài)并不是立馬生效,通常還會(huì)有一些 loading 效果。所以第一個(gè)任務(wù)作為立即執(zhí)行任務(wù),而第二個(gè)任務(wù)就可以視為過(guò)渡任務(wù)。

          WechatIMG6496.jpeg

          useTransition 基礎(chǔ)介紹:

          useTransition 執(zhí)行返回一個(gè)數(shù)組。數(shù)組有兩個(gè)狀態(tài)值:

          • 第一個(gè)是,當(dāng)處于過(guò)渡狀態(tài)的標(biāo)志——isPending。
          • 第二個(gè)是一個(gè)方法,可以理解為上述的 startTransition??梢园牙锩娴母氯蝿?wù)變成過(guò)渡任務(wù)。
          import { useTransition } from 'react' 
          /* 使用 */
          const  [ isPending , startTransition ] = useTransition ()

          useTransition 基礎(chǔ)用法:

          除了上述切換 tab 場(chǎng)景外,還有很多場(chǎng)景非常適合 useTransition 產(chǎn)生的過(guò)渡任務(wù),比如輸入內(nèi)容,實(shí)時(shí)搜索并展示數(shù)據(jù),這本質(zhì)上也是有兩個(gè)優(yōu)先級(jí)的任務(wù),第一個(gè)任務(wù)就是受控表單的實(shí)時(shí)響應(yīng);第二個(gè)就是輸入內(nèi)容改變,數(shù)據(jù)展示的變化。那么接下來(lái)我們寫(xiě)一個(gè) demo 來(lái)看一下 useTransition 的基本使用。

          /* 模擬數(shù)據(jù) */
          const mockList1 = new Array(10000).fill('tab1').map((item,index)=>item+'--'+index )
          const mockList2 = new Array(10000).fill('tab2').map((item,index)=>item+'--'+index )
          const mockList3 = new Array(10000).fill('tab3').map((item,index)=>item+'--'+index )

          const tab = {
            tab1: mockList1,
            tab2: mockList2,
            tab3: mockList3
          }

          export default function Index(){
            const [ active, setActive ] = React.useState('tab1'//需要立即響應(yīng)的任務(wù),立即更新任務(wù)
            const [ renderData, setRenderData ] = React.useState(tab[active]) //不需要立即響應(yīng)的任務(wù),過(guò)渡任務(wù)
            const [ isPending,startTransition  ] = React.useTransition() 
            const handleChangeTab = (activeItem) => {
               setActive(activeItem) // 立即更新
               startTransition(()=>// startTransition 里面的任務(wù)優(yōu)先級(jí)低
                 setRenderData(tab[activeItem])
               })
            }
            return <div>
              <div className='tab' >
                 { Object.keys(tab).map((item)=> <span className={ active === item && 'active' } onClick={()=>handleChangeTab(item)} >{ item }</span> ) }
              </div>
              <ul className='content' >
                 { isPending && <div> loading... </div> }
                 { renderData.map(item=> <li key={item} >{item}</li>) }
              </ul>
            </div>

          }

          如上當(dāng)切換 tab 的時(shí)候,產(chǎn)生了兩個(gè)優(yōu)先級(jí)任務(wù),第一個(gè)任務(wù)是 setActive 控制 tab active 狀態(tài)的改變,第二個(gè)任務(wù)為 setRenderData 控制渲染的長(zhǎng)列表數(shù)據(jù) (在現(xiàn)實(shí)場(chǎng)景下長(zhǎng)列表可能是一些數(shù)據(jù)量大的可視化圖表)。

          2.5 useDeferredValue

          React 18 提供了 useDeferredValue 可以讓狀態(tài)滯后派生。useDeferredValue 的實(shí)現(xiàn)效果也類(lèi)似于 transtion,當(dāng)迫切的任務(wù)執(zhí)行后,再得到新的狀態(tài),而這個(gè)新的狀態(tài)就稱(chēng)之為 DeferredValue。

          useDeferredValue 基礎(chǔ)介紹:

          useDeferredValue 和上述 useTransition 本質(zhì)上有什么異同呢?


          相同點(diǎn): useDeferredValue 本質(zhì)上和內(nèi)部實(shí)現(xiàn)與 useTransition 一樣都是標(biāo)記成了過(guò)渡更新任務(wù)。

          不同點(diǎn): useTransition 是把 startTransition 內(nèi)部的更新任務(wù)變成了過(guò)渡任務(wù)transtion,而 useDeferredValue 是把原值通過(guò)過(guò)渡任務(wù)得到新的值,這個(gè)值作為延時(shí)狀態(tài)。一個(gè)是處理一段邏輯,另一個(gè)是生產(chǎn)一個(gè)新的狀態(tài)。

          useDeferredValue 接受一個(gè)參數(shù) value ,一般為可變的 state , 返回一個(gè)延時(shí)狀態(tài) deferrredValue。

          const deferrredValue = React.useDeferredValue(value)

          useDeferredValue 基礎(chǔ)用法:

          接下來(lái)把上邊那個(gè)例子用 useDeferredValue 來(lái)實(shí)現(xiàn)。

          export default function Index(){
            const [ active, setActive ] = React.useState('tab1'//需要立即響應(yīng)的任務(wù),立即更新任務(wù)
            const deferActive = React.useDeferredValue(active) // 把狀態(tài)延時(shí)更新,類(lèi)似于過(guò)渡任務(wù)
            const handleChangeTab = (activeItem) => {
               setActive(activeItem) // 立即更新
            }
            const renderData = tab[deferActive] // 使用滯后狀態(tài)
            return <div>
              <div className='tab' >
                 { Object.keys(tab).map((item)=> <span className={ active === item && 'active' } onClick={()=>handleChangeTab(item)} >{ item }</span> ) }
              </div>
              <ul className='content' >
                 { renderData.map(item=> <li key={item} >{item}</li>) }
              </ul>
            </div>

            }

          如上 active 為正常改變的狀態(tài),deferActive 為滯后的 active 狀態(tài),我們使用正常狀態(tài)去改變 tab 的 active 狀態(tài),使用滯后的狀態(tài)去更新視圖,同樣達(dá)到了提升用戶(hù)體驗(yàn)的作用。

          三 hooks 之執(zhí)行副作用

          3.1 useEffect

          React hooks也提供了 api ,用于彌補(bǔ)函數(shù)組件沒(méi)有生命周期的缺陷。其本質(zhì)主要是運(yùn)用了 hooks 里面的 useEffect , useLayoutEffect,還有 useInsertionEffect。其中最常用的就是 useEffect 。我們首先來(lái)看一下 useEffect 的使用。

          useEffect 基礎(chǔ)介紹:

          useEffect(()=>{
              return destory
          },dep)

          useEffect 第一個(gè)參數(shù) callback, 返回的 destory , destory 作為下一次callback執(zhí)行之前調(diào)用,用于清除上一次 callback 產(chǎn)生的副作用。

          第二個(gè)參數(shù)作為依賴(lài)項(xiàng),是一個(gè)數(shù)組,可以有多個(gè)依賴(lài)項(xiàng),依賴(lài)項(xiàng)改變,執(zhí)行上一次callback 返回的 destory ,和執(zhí)行新的 effect 第一個(gè)參數(shù) callback 。

          對(duì)于 useEffect 執(zhí)行, React 處理邏輯是采用異步調(diào)用 ,對(duì)于每一個(gè) effect 的 callback, React 會(huì)向 setTimeout回調(diào)函數(shù)一樣,放入任務(wù)隊(duì)列,等到主線(xiàn)程任務(wù)完成,DOM 更新,js 執(zhí)行完成,視圖繪制完畢,才執(zhí)行。所以 effect 回調(diào)函數(shù)不會(huì)阻塞瀏覽器繪制視圖。

          useEffect 基礎(chǔ)用法:

          /* 模擬數(shù)據(jù)交互 */
          function getUserInfo(a){
              return new Promise((resolve)=>{
                  setTimeout(()=>
                     resolve({
                         name:a,
                         age:16,
                     }) 
                  },500)
              })
          }

          const Demo = ({ a }) => {
              const [ userMessage , setUserMessage ] :any= useState({})
              const div= useRef()
              const [number, setNumber] = useState(0)
              /* 模擬事件監(jiān)聽(tīng)處理函數(shù) */
              const handleResize =()=>{}
              /* useEffect使用 ,這里如果不加限制 ,會(huì)是函數(shù)重復(fù)執(zhí)行,陷入死循環(huán)*/
              useEffect(()=>{
                 /* 請(qǐng)求數(shù)據(jù) */
                 getUserInfo(a).then(res=>{
                     setUserMessage(res)
                 })
                 /* 定時(shí)器 延時(shí)器等 */
                 const timer = setInterval(()=>console.log(666),1000)
                 /* 操作dom  */
                 console.log(div.current) /* div */
                 /* 事件監(jiān)聽(tīng)等 */
                 window.addEventListener('resize', handleResize)
                   /* 此函數(shù)用于清除副作用 */
                 return function(){
                     clearInterval(timer) 
                     window.removeEventListener('resize', handleResize)
                 }
              /* 只有當(dāng)props->a和state->number改變的時(shí)候 ,useEffect副作用函數(shù)重新執(zhí)行 ,如果此時(shí)數(shù)組為空[],證明函數(shù)只有在初始化的時(shí)候執(zhí)行一次相當(dāng)于componentDidMount */
              },[ a ,number ])
              return (<div ref={div} >
                  <span>{ userMessage.name }</span>
                  <span>{ userMessage.age }</span>
                  <div onClick={ ()=> setNumber(1) } >{ number }</div>
              </div>
          )
          }

          如上在 useEffect 中做的功能如下:

          • ① 請(qǐng)求數(shù)據(jù)。
          • ② 設(shè)置定時(shí)器,延時(shí)器等。
          • ③ 操作 dom , 在 React Native 中可以通過(guò) ref 獲取元素位置信息等內(nèi)容。
          • ④ 注冊(cè)事件監(jiān)聽(tīng)器, 事件綁定,在 React Native 中可以注冊(cè) NativeEventEmitter 。
          • ⑤ 還可以清除定時(shí)器,延時(shí)器,解綁事件監(jiān)聽(tīng)器等。

          3.2 useLayoutEffect

          useLayoutEffect 基礎(chǔ)介紹:

          useLayoutEffect 和 useEffect 不同的地方是采用了同步執(zhí)行,那么和useEffect有什么區(qū)別呢?

          ① 首先 useLayoutEffect 是在 DOM 更新之后,瀏覽器繪制之前,這樣可以方便修改 DOM,獲取 DOM 信息,這樣瀏覽器只會(huì)繪制一次,如果修改 DOM 布局放在 useEffect ,那 useEffect 執(zhí)行是在瀏覽器繪制視圖之后,接下來(lái)又改 DOM ,就可能會(huì)導(dǎo)致瀏覽器再次回流和重繪。而且由于兩次繪制,視圖上可能會(huì)造成閃現(xiàn)突兀的效果。

          ② useLayoutEffect callback 中代碼執(zhí)行會(huì)阻塞瀏覽器繪制。

          useEffect 基礎(chǔ)用法:

          const DemoUseLayoutEffect = () => {
              const target = useRef()
              useLayoutEffect(() => {
                  /*我們需要在dom繪制之前,移動(dòng)dom到制定位置*/
                  const { x ,y } = getPositon() /* 獲取要移動(dòng)的 x,y坐標(biāo) */
                  animate(target.current,{ x,y })
              }, []);
              return (
                  <div >
                      <span ref={ target } className="animate"></span>
                  </div>

              )
          }

          3.3 useInsertionEffect

          useInsertionEffect 基礎(chǔ)介紹:

          useInsertionEffect 是在 React v18 新添加的 hooks ,它的用法和 useEffect 和 useLayoutEffect 一樣。那么這個(gè) hooks 用于什么呢?

          在介紹 useInsertionEffect 用途之前,先看一下 useInsertionEffect 的執(zhí)行時(shí)機(jī)。

          React.useEffect(()=>{
              console.log('useEffect 執(zhí)行')
          },[])

          React.useLayoutEffect(()=>{
              console.log('useLayoutEffect 執(zhí)行')
          },[])

          React.useInsertionEffect(()=>{
              console.log('useInsertionEffect 執(zhí)行')
          },[])

          打印:useInsertionEffect 執(zhí)行 -> useLayoutEffect 執(zhí)行 -> useEffect 執(zhí)行

          可以看到 useInsertionEffect 的執(zhí)行時(shí)機(jī)要比 useLayoutEffect 提前,useLayoutEffect 執(zhí)行的時(shí)候 DOM 已經(jīng)更新了,但是在 useInsertionEffect 的執(zhí)行的時(shí)候,DOM 還沒(méi)有更新。本質(zhì)上 useInsertionEffect 主要是解決 CSS-in-JS 在渲染中注入樣式的性能問(wèn)題。這個(gè) hooks 主要是應(yīng)用于這個(gè)場(chǎng)景,在其他場(chǎng)景下 React 不期望用這個(gè) hooks 。

          useInsertionEffect 模擬使用:

          export default function Index(){

            React.useInsertionEffect(()=>{
               /* 動(dòng)態(tài)創(chuàng)建 style 標(biāo)簽插入到 head 中 */
               const style = document.createElement('style')
               style.innerHTML = `
                 .css-in-js{
                   color: red;
                   font-size: 20px;
                 }
               `

               document.head.appendChild(style)
            },[])

            return <div className="css-in-js" > hello , useInsertionEffect </div>
          }

          如上模擬了 useInsertionEffect 的使用。

          四 hooks 之狀態(tài)獲取與傳遞

          4.1 useContext

          useContext 基礎(chǔ)介紹

          可以使用 useContext ,來(lái)獲取父級(jí)組件傳遞過(guò)來(lái)的 context 值,這個(gè)當(dāng)前值就是最近的父級(jí)組件 Provider 設(shè)置的 value 值,useContext 參數(shù)一般是由 createContext 方式創(chuàng)建的 ,也可以父級(jí)上下文 context 傳遞的 ( 參數(shù)為 context )。useContext 可以代替 context.Consumer 來(lái)獲取 Provider 中保存的 value 值。

          const contextValue = useContext(context)

          useContext 接受一個(gè)參數(shù),一般都是 context 對(duì)象,返回值為 context 對(duì)象內(nèi)部保存的 value 值。

          useContext 基礎(chǔ)用法:

          /* 用useContext方式 */
          const DemoContext = ()=> {
              const value:any = useContext(Context)
              /* my name is alien */
          return <div> my name is { value.name }</div>
          }

          /* 用Context.Consumer 方式 */
          const DemoContext1 = ()=>{
              return <Context.Consumer>
                   {/*  my name is alien  */}
                  { (value)=> <div> my name is { value.name }</div> }
              </Context.Consumer>

          }

          export default ()=>{
              return <div>
                  <Context.Provider value={{ name:'alien' , age:18 }} >
                      <DemoContext />
                      <DemoContext1 />
                  </Context.Provider>
              </div>

          }

          4.2 useRef

          useRef 基礎(chǔ)介紹:

          useRef 可以用來(lái)獲取元素,緩存狀態(tài),接受一個(gè)狀態(tài) initState 作為初始值,返回一個(gè) ref 對(duì)象 cur, cur 上有一個(gè) current 屬性就是 ref 對(duì)象需要獲取的內(nèi)容。

          const cur = React.useRef(initState)
          console.log(cur.current)

          useRef 基礎(chǔ)用法:

          useRef 獲取 DOM 元素,在 React Native 中雖然沒(méi)有 DOM 元素,但是也能夠獲取組件的節(jié)點(diǎn)信息( fiber 信息 )。

          const DemoUseRef = ()=>{
              const dom= useRef(null)
              const handerSubmit = ()=>{
                  /*  <div >表單組件</div>  dom 節(jié)點(diǎn) */
                  console.log(dom.current)
              }
              return <div>
                  {/* ref 標(biāo)記當(dāng)前dom節(jié)點(diǎn) */}
                  <div ref={dom} >表單組件</div>
                  <button onClick={()=>handerSubmit()} >提交</button> 
              </div>

          }

          如上通過(guò) useRef 來(lái)獲取 DOM 節(jié)點(diǎn)。

          useRef 保存狀態(tài), 可以利用 useRef 返回的 ref 對(duì)象來(lái)保存狀態(tài),只要當(dāng)前組件不被銷(xiāo)毀,那么狀態(tài)就會(huì)一直存在。

          const status = useRef(false)
          /* 改變狀態(tài) */
          const handleChangeStatus = () => {
            status.current = true
          }

          4.3 useImperativeHandle

          useImperativeHandle 基礎(chǔ)介紹:

          useImperativeHandle 可以配合 forwardRef 自定義暴露給父組件的實(shí)例值。這個(gè)很有用,我們知道,對(duì)于子組件,如果是 class 類(lèi)組件,我們可以通過(guò) ref 獲取類(lèi)組件的實(shí)例,但是在子組件是函數(shù)組件的情況,如果我們不能直接通過(guò) ref 的,那么此時(shí) useImperativeHandle 和 forwardRef 配合就能達(dá)到效果。

          useImperativeHandle 接受三個(gè)參數(shù):

          • ① 第一個(gè)參數(shù)ref: 接受 forWardRef 傳遞過(guò)來(lái)的 ref。
          • ② 第二個(gè)參數(shù) createHandle :處理函數(shù),返回值作為暴露給父組件的 ref 對(duì)象。
          • ③ 第三個(gè)參數(shù) deps : 依賴(lài)項(xiàng) deps ,依賴(lài)項(xiàng)更改形成新的 ref 對(duì)象。

          useImperativeHandle 基礎(chǔ)用法:

          我們來(lái)模擬給場(chǎng)景,用useImperativeHandle,使得父組件能讓子組件中的input自動(dòng)賦值并聚焦。

          function Son (props,ref{
              console.log(props)
              const inputRef = useRef(null)
              const [ inputValue , setInputValue ] = useState('')
              useImperativeHandle(ref,()=>{
                 const handleRefs = {
                     /* 聲明方法用于聚焦input框 */
                     onFocus(){
                        inputRef.current.focus()
                     },
                     /* 聲明方法用于改變input的值 */
                     onChangeValue(value){
                         setInputValue(value)
                     }
                 }
                 return handleRefs
              },[])
              return <div>
                  <input
                      placeholder="請(qǐng)輸入內(nèi)容"
                      ref={inputRef}
                      value={inputValue}
                  />

              </div>

          }

          const ForwarSon = forwardRef(Son)

          class Index extends React.Component{
              inputRef = null
              handerClick(){
                 const { onFocus , onChangeValue } =this.cur
                 onFocus()
                 onChangeValue('let us learn React!')
              }
              render(){
                  return <div style={{ marginTop:'50px' }} >
                      <ForwarSon ref={node => (this.inputRef = node)} />
                      <button onClick={this.handerClick.bind(this)} >操控子組件</button>
                  </div>

              }
          }

          效果:

          8e8c05f0c82c43719079d4db9536abc0_tplv-k3u1fbpfcp-watermark.gif

          五 hooks 之狀態(tài)派生與保存

          5.1 useMemo

          useMemo 可以在函數(shù)組件 render 上下文中同步執(zhí)行一個(gè)函數(shù)邏輯,這個(gè)函數(shù)的返回值可以作為一個(gè)新的狀態(tài)緩存起來(lái)。那么這個(gè) hooks 的作用就顯而易見(jiàn)了:

          場(chǎng)景一:在一些場(chǎng)景下,需要在函數(shù)組件中進(jìn)行大量的邏輯計(jì)算,那么我們不期望每一次函數(shù)組件渲染都執(zhí)行這些復(fù)雜的計(jì)算邏輯,所以就需要在 useMemo 的回調(diào)函數(shù)中執(zhí)行這些邏輯,然后把得到的產(chǎn)物(計(jì)算結(jié)果)緩存起來(lái)就可以了。

          場(chǎng)景二:React 在整個(gè)更新流程中,diff 起到了決定性的作用,比如 Context 中的 provider 通過(guò) diff value 來(lái)判斷是否更新

          useMemo 基礎(chǔ)介紹:

          const cacheSomething = useMemo(create,deps)
          • ① create:第一個(gè)參數(shù)為一個(gè)函數(shù),函數(shù)的返回值作為緩存值,如上 demo 中把 Children 對(duì)應(yīng)的 element 對(duì)象,緩存起來(lái)。
          • ② deps:第二個(gè)參數(shù)為一個(gè)數(shù)組,存放當(dāng)前 useMemo 的依賴(lài)項(xiàng),在函數(shù)組件下一次執(zhí)行的時(shí)候,會(huì)對(duì)比 deps 依賴(lài)項(xiàng)里面的狀態(tài),是否有改變,如果有改變重新執(zhí)行 create ,得到新的緩存值。
          • ③ acheSomething:返回值,執(zhí)行 create 的返回值。如果 deps 中有依賴(lài)項(xiàng)改變,返回的重新執(zhí)行 create 產(chǎn)生的值,否則取上一次緩存值。

          useMemo 基礎(chǔ)用法:

          派生新?tīng)顟B(tài):

          function Scope({
              const keeper = useKeep()
              const { cacheDispatch, cacheList, hasAliveStatus } = keeper
             
              /* 通過(guò) useMemo 得到派生出來(lái)的新?tīng)顟B(tài) contextValue  */
              const contextValue = useMemo(() => {
                  return {
                      cacheDispatch: cacheDispatch.bind(keeper),
                      hasAliveStatus: hasAliveStatus.bind(keeper),
                      cacheDestory(payload) => cacheDispatch.call(keeper, { type: ACTION_DESTORY, payload })
                  }
                
              }, [keeper])
              return <KeepaliveContext.Provider value={contextValue}>
              </KeepaliveContext.Provider>

          }

          如上通過(guò) useMemo 得到派生出來(lái)的新?tīng)顟B(tài) contextValue ,只有 keeper 變化的時(shí)候,才改變 Provider 的 value 。

          緩存計(jì)算結(jié)果:

          function Scope(){
              const style = useMemo(()=>{
                let computedStyle = {}
                // 經(jīng)過(guò)大量的計(jì)算
                return computedStyle
              },[])
              return <div style={style} ></div>
          }

          緩存組件,減少子組件 rerender 次數(shù):

          function Scope ({ children }){
             const renderChild = useMemo(()=>{ children()  },[ children ])
             return <div>{ renderChild } </div>
          }

          5.2 useCallback

          useCallback 基礎(chǔ)介紹:

          useMemo 和 useCallback 接收的參數(shù)都是一樣,都是在其依賴(lài)項(xiàng)發(fā)生變化后才執(zhí)行,都是返回緩存的值,區(qū)別在于 useMemo 返回的是函數(shù)運(yùn)行的結(jié)果,useCallback 返回的是函數(shù),這個(gè)回調(diào)函數(shù)是經(jīng)過(guò)處理后的也就是說(shuō)父組件傳遞一個(gè)函數(shù)給子組件的時(shí)候,由于是無(wú)狀態(tài)組件每一次都會(huì)重新生成新的 props 函數(shù),這樣就使得每一次傳遞給子組件的函數(shù)都發(fā)生了變化,這時(shí)候就會(huì)觸發(fā)子組件的更新,這些更新是沒(méi)有必要的,此時(shí)我們就可以通過(guò) usecallback 來(lái)處理此函數(shù),然后作為 props 傳遞給子組件。

          useCallback 基礎(chǔ)用法:

          /* 用react.memo */
          const DemoChildren = React.memo((props)=>{
             /* 只有初始化的時(shí)候打印了 子組件更新 */
              console.log('子組件更新')
             useEffect(()=>{
                 props.getInfo('子組件')
             },[])
             return <div>子組件</div>
          })

          const DemoUseCallback=({ id })=>{
              const [number, setNumber] = useState(1)
              /* 此時(shí)usecallback的第一參數(shù) (sonName)=>{ console.log(sonName) }
               經(jīng)過(guò)處理賦值給 getInfo */

              const getInfo  = useCallback((sonName)=>{
                    console.log(sonName)
              },[id])
              return <div>
                  {/* 點(diǎn)擊按鈕觸發(fā)父組件更新 ,但是子組件沒(méi)有更新 */}
                  <button onClick={ ()=>setNumber(number+1) } >增加</button>
                  <DemoChildren getInfo={getInfo} />
              </div>

          }

          六 hooks 之工具 hooks

          6.1 useDebugValue

          我們不推薦你向每個(gè)自定義 Hook 添加 debug 值。當(dāng)它作為共享庫(kù)的一部分時(shí)才最有價(jià)值。在某些情況下,格式化值的顯示可能是一項(xiàng)開(kāi)銷(xiāo)很大的操作。除非需要檢查 Hook,否則沒(méi)有必要這么做。因此,useDebugValue 接受一個(gè)格式化函數(shù)作為可選的第二個(gè)參數(shù)。該函數(shù)只有在 Hook 被檢查時(shí)才會(huì)被調(diào)用。它接受 debug 值作為參數(shù),并且會(huì)返回一個(gè)格式化的顯示值。

          useDebugValue 基礎(chǔ)介紹:

          useDebugValue 可用于在 React 開(kāi)發(fā)者工具中顯示自定義 hook 的標(biāo)簽。這個(gè)hooks目的就是檢查自定義hooks。

          useDebugValue 基本使用:

          function useFriendStatus(friendID{
            const [isOnline, setIsOnline] = useState(null);
            // ...
            // 在開(kāi)發(fā)者工具中的這個(gè) Hook 旁邊顯示標(biāo)簽
            // e.g. "FriendStatus: Online"
            useDebugValue(isOnline ? 'Online' : 'Offline');

            return isOnline;
          }

          6.2 useId

          useID 基礎(chǔ)介紹:

          useId 也是 React v18 產(chǎn)生的新的 hooks , 它可以在 client 和 server 生成唯一的 id , 解決了在服務(wù)器渲染中,服務(wù)端和客戶(hù)端產(chǎn)生 id 不一致的問(wèn)題,更重要的是保障了 React v18 中 streaming renderer (流式渲染) 中 id 的穩(wěn)定性。

          低版本 React ssr 存在的問(wèn)題:

          比如在一些項(xiàng)目或者是開(kāi)源庫(kù)中用 Math.random() 作為 ID 的時(shí)候,可以會(huì)有一些隨機(jī)生成 id 的場(chǎng)景:

          const rid = Math.random() + '_id_'  /* 生成一個(gè)隨機(jī)id  */
          function Demo (){
             // 使用 rid 
             return <div id={rid} ></div>
          }

          這在純客戶(hù)端渲染中沒(méi)有問(wèn)題,但是在服務(wù)端渲染的時(shí)候,傳統(tǒng)模式下需要走如下流程:

          e54da686-6d8e-4431-a378-c05ac49cb6fb.png

          在這個(gè)過(guò)程中,當(dāng)服務(wù)端渲染到 html 和 hydrate 過(guò)程分別在服務(wù)端和客戶(hù)端進(jìn)行,但是會(huì)走兩遍 id 的生成流程,這樣就會(huì)造成 id不一致的情況發(fā)生。useId 的出現(xiàn)能有效的解決這個(gè)問(wèn)題。

          useId 基本用法:

          function Demo (){
             const rid = useId() // 生成穩(wěn)定的 id 
             return <div id={rid} ></div>
          }

          v18 ssr

          在 React v18 中 對(duì) ssr 增加了流式渲染的特性 New Suspense SSR Architecture in React 18 , 那么這個(gè)特性是什么呢?我們來(lái)看一下:

          在傳統(tǒng) React ssr 中,如果正常情況下, hydrate 過(guò)程如下所示:

          WechatIMG6936.jpeg

          剛開(kāi)始的時(shí)候,因?yàn)榉?wù)端渲染,只會(huì)渲染 html 結(jié)構(gòu),此時(shí)還沒(méi)注入 js 邏輯,所以我們把它用灰色不能交互的模塊表示。(如上灰色的模塊不能做用戶(hù)交互,比如點(diǎn)擊事件之類(lèi)的。)

          hydrate js 加載之后,此時(shí)的模塊可以正常交互,所以用綠色的模塊展示。

          但是如果其中一個(gè)模塊,服務(wù)端請(qǐng)求數(shù)據(jù),數(shù)據(jù)量比較大,耗費(fèi)時(shí)間長(zhǎng),我們不期望在服務(wù)端完全形成 html 之后在渲染,那么 React 18 給了一個(gè)新的可能性??梢允褂?suspense>包裝頁(yè)面的一部分,然后讓這一部分的內(nèi)容先掛起。

          接下來(lái)會(huì)通過(guò) script 加載 js 的方式 流式注入 html 代碼的片段,來(lái)補(bǔ)充整個(gè)頁(yè)面。接下來(lái)的流程如下所示:

          d94d8ddb-bdcd-4be8-a851-4927c7966b99.png

          在這個(gè)原理基礎(chǔ)之上, React 這個(gè)特性叫 Selective Hydration,可以根據(jù)用戶(hù)交互改變 hydrate 的順序。

          比如有兩個(gè)模塊都是通過(guò) Suspense 掛起的,當(dāng)兩個(gè)模塊發(fā)生交互邏輯時(shí),會(huì)根據(jù)交互來(lái)選擇性地改變 hydrate 的順序。

          ede45613-9994-4e77-9f50-5b7c1faf1160.png

          如上 C D 選擇性的 hydrate 就是 Selective Hydration 的結(jié)果。那么回到主角 useId 上,如果在 hydrate 過(guò)程中,C D 模塊 id 是動(dòng)態(tài)生成的,比如如下:

          let id = 0
          function makeId(){
            return id++
          }
          function Demo(){
            const id = useRef( makeId() )
            return <div id={id}  >...</div>
          }

          那么如果組件是 Selective Hydration , 那么注冊(cè)組件的順序服務(wù)端和客戶(hù)端有可能不統(tǒng)一,這樣表現(xiàn)就會(huì)不一致了。那么用 useId 動(dòng)態(tài)生成 id 就不會(huì)有這個(gè)問(wèn)題產(chǎn)生了,所以說(shuō) useId 保障了 React v18 中 streaming renderer (流式渲染) 中 id 的穩(wěn)定性。

          七 總結(jié)

          本文詳細(xì)介紹了 React Hooks 產(chǎn)生初衷以及 React Hooks,希望看到這篇文章的同學(xué),可以記住每一個(gè) hooks 的使用場(chǎng)景,在項(xiàng)目中熟練使用起來(lái)。

          參考文檔

          • streaming renderer
          • react-hooks如何使用?
          • React進(jìn)階實(shí)踐指南


          瀏覽 82
          點(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>
                  青青草在线一人 | 国产美女插逼 | 欧美日韩激情 | 在线国产网站 | 国产视频一区二区三区四 |