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

          一些關(guān)于react的keep-alive功能相關(guān)知識在這里(下)

          共 6376字,需瀏覽 13分鐘

           ·

          2022-05-13 08:59

          點(diǎn)擊上方?前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群

          一些關(guān)于react的keep-alive功能相關(guān)知識在這里(下)

          本篇承接上篇內(nèi)部, 所以是從第九點(diǎn)開始

          九、保留頁面scroll

          比如頁面上的table里有100條數(shù)據(jù), 我們想看第100條數(shù)據(jù), 那就要滾動不少距離, 不少場景這種滾動距離也是有必要保留的。

          這里使用的方法其實(shí)比較傳統(tǒng)啦, 首先在KeepAliveProvider 下發(fā)一個處理滾動的方法:

          ?const?handleScroll?=?useCallback(
          ????????(cacheId,?event)?=>?{
          ????????????if?(catheStates?.[cacheId])?{
          ????????????????const?target?=?event.target
          ????????????????const?scrolls?=?catheStates[cacheId].scrolls
          ????????????????scrolls[target]?=?target.scrollTop
          ????????????}
          ????????},
          ????????[catheStates]
          ????)

          Keeper組件里面接收并執(zhí)行:

          ?const?{?dispatch,?mount,?handleScroll?}?=?useContext(CacheContext)

          ????useEffect(()?=>?{
          ????????const?onScroll?=?handleScroll.bind(null,?cacheId)
          ????????(divRef?.current?as?any)?.addEventListener?.('scroll',?onScroll,?true)
          ????????return?(divRef?.current?as?any)?.addEventListener?.('scroll',?onScroll,?true)
          ????},?[handleScroll])

          在Keeper里面將滾動屬性賦予元素:

          ?useEffect(()?=>?{
          ????????const?catheState?=?catheStates[cacheId]
          ????????if?(catheState?&&?catheState.doms)?{
          ????????????const?doms?=?catheState.doms
          ????????????doms.forEach((dom:?any)?=>?{
          ??????????????(divRef?.current?as?any)?.appendChild?.(dom)
          ????????????})

          ????????//?新增
          ????????doms.forEach((dom:?any)?=>?{
          ????????????if?(catheState.scrolls[dom])?{
          ????????????????dom.scrollTop?=?catheState.scrolls[dom]
          ????????????}
          ????????})
          ????????}?else?{
          ????????????mount({
          ????????????????cacheId,
          ????????????????reactElement:?props.children
          ????????????})
          ????????}
          ????},?[catheStates])

          這里如果不主動增加賦予scroll的方法的話, 滾動距離是不會被保存的, 因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Keeper每次都是新的。

          十、KeepAliveProvider內(nèi)部 Keeper子組件內(nèi)部的CacheContext

          我們是把組件渲染在 KeepAliveProvider 里面, 那么如果某個Provider是在 KeepAliveProvider 內(nèi)部定義的, 則KeepAliveProvider級別的組件是無法使用 Consumer 拿到這個值的。

          這里就引出一個問題, 如何將 KeepAliveProvider 中的組件的上下文, 修改為Keeper組件的上下文。

          這里演示一下最直接的方式, 讓用戶傳入Provider與其value值。

          ????cacheId="home"?
          ???context={{?Provider:?Provider,?value:?value?}}>
          ????<Home?/>
          ?</Keeper>

          我們拿到這兩個值后直接在Keeper中修改reactElement的結(jié)構(gòu):

          ?mount({
          ????????cacheId,
          ????????reactElement:?context???
          ??????????<context.Provider?
          ?????????????value={context.value}>
          {props.children}context.Provider>
          ?:?
          ??????????props.children
          ????})

          當(dāng)檢測到context有值則直接在 props.children 外面套一層, 當(dāng)然這里存在一個多層Provider嵌套的問題沒有去解決, 因?yàn)橹饾u復(fù)雜起來它的實(shí)用性已經(jīng)在下降了, 接下來還有新的bug來襲。

          十一、需要傳值的組件

          大家有沒有發(fā)現(xiàn)上述組件所有邏輯, 都是直接寫在Keeper標(biāo)簽里面的, 并沒有任何的傳值, 但是比較常見的一種場景是下面這樣的:

          function?Root?(){
          ???const?[n,?setN]?=?useState(1)
          ?return?
          ???(
          ????<>
          ???????<button?onClick={()=>setN(n+1)}>n+1button>

          ????????<Keeper>
          ??????????<Home?n={n}?/>
          ????????Keeper>
          ????
          ?)
          }

          這個nKeeper外層傳遞給Home組件的, 這種寫法下會導(dǎo)致n雖然變化了但是Home里面不會響應(yīng)。

          這個bug我是這樣發(fā)現(xiàn)的, 當(dāng)我把這個插件用在我們團(tuán)隊(duì)的項(xiàng)目里的一個表格為主的頁面時 , table一直顯示是空的, 并且輸入框也無法輸入值, 經(jīng)過測試發(fā)現(xiàn)其實(shí)值是有變化的, 只是沒有展示在組件的dom上。

          嘗試了好久后試了下react-activation 很遺憾它也有相同的問題, 那其實(shí)就說明這個bug很可能無法解決或者就是這個插件本身的架構(gòu)存在的問題。

          十二、為何這么奇怪的bug場景

          當(dāng)時這個bug折磨了我一天半的時間, 最后定位到外界的傳參已經(jīng)不能算是這個組件本身的參數(shù)了, 我們組件的實(shí)際渲染位置是 KeepAliveProvider 的第一層, 而Keeper的外層還在KeepAliveProvider的更內(nèi)層, 這就導(dǎo)致這些值的變化其實(shí)是沒有能夠影響到組件。

          可以理解為這些值的變化, 比如n的變化就如同window.n的改變一樣, react組件是不會去響應(yīng)這個變化的。

          那其實(shí)我們要做的就是讓外層傳入的值的變化, 可以帶動組件的樣式變化 (逐漸入坑!)。

          十三、將props單獨(dú)拿出來

          我借鑒了網(wǎng)上另一種keep-alive組件的寫法, 把Keeper組件改為一個keeper的方法, 這個方法返回一個組件看, 這樣就可以接收一個props了, 也就把變量圈定在props這個范圍:

          const?Home?=?keeper(HomePage,?{?cacheId:?'home'?})


          function?Root(){
          ?const?[n,?setN]?=?useState(1)
          ??return?(
          ????<>
          ?????<button?onClick={()=>setN(n+1)}>n+1button>

          ?????<Home?n={n}>?//?此處可以傳值了
          ????
          ??)
          }

          這樣做的目的是讓開發(fā)者把能夠影響組件狀態(tài)的參數(shù)一口氣傳進(jìn)來, 比如之前一個Keeper里面可以有多個組件, 這種情況就不好控制哪些參數(shù)變化會導(dǎo)致哪些組件更新, 但以組件的方式可以明顯得知組件接收到的props里面的值的改變會導(dǎo)致組件更新。

          我想到的方案是, 在KeepAliveProvider里面新建propsObj, 用來專門儲存每個緩存組件的props, 之所以如此設(shè)計(jì)將其單獨(dú)拿出來, 是要把傳參與組件的邏輯拆分開, 不少邏輯會監(jiān)控catheStates的變化而執(zhí)行, 但是props的變化沒有必要觸發(fā)這些。

          ?const?[propsObj,?setPropsObj]?=?useState();
          ????return?(
          ????<CacheContext.Provider?value={{?setPropsObj,?propsObj?}}>
          ??????{props.children}
          ??????
          ???//....?略?

          KeepAliveProvider 里面的渲染需要變一個形式, reactElement 變成組件了, 別忘了名字要變成大寫的。

          ?//?舊的
          ????//?{reactElement}
          ????
          ????//?新的
          ????{propsObj?&&?
          ??????<ReactElement?{...propsObj[cacheId]}>ReactElement>}

          改裝一下Keeper文件, 首先要把文件名改為 keeper, 導(dǎo)出的方法要進(jìn)行一下更改。

          ?export?default?function?(
          ???RealComponent:?React.FunctionComponent,?{?cacheId?=?''?}
          )?
          {
          ???
          ???return?function?Keeper(props:?any)?{
          ?//?...?略

          Keeper內(nèi)mount方法的使用也稍作調(diào)整:

          ?mount({
          ????????cacheId,
          ????????ReactElement:?RealComponent
          ????})

          關(guān)鍵的來了, 我們要在Keeper里面監(jiān)測props的變化, 來更新propsObj:

          const?{?propsObj,?setPropsObj?}?=?useContext(CacheContext)

          ????useEffect(()?=>?{
          ????????setPropsObj({
          ????????????...propsObj,
          ????????????[cacheId]:?props
          ????????})
          ????},?[props])?

          十四、緩存失效的bug

          上述我們已經(jīng)把插件改裝了形式, 并且發(fā)現(xiàn)可以讓如下場景正常渲染, Home組件的props是外界傳入的:

          ?const?Home?=?keeper(HomePage,?{?cacheId:?'home'?})

          const?RootComponent:?React.FC?=?()?=>?{
          ????return?(
          ????????<KeepAliveProvider>
          ????????????<Router>
          ????????????????<Routes>
          ????????????????????<Route?path={'/'}?element={<Mid?/>}?/>
          ????????????????Routes>

          ????????????Router>
          ????????KeepAliveProvider>
          ????)
          }
          function?Mid()?{
          ????const?[n,?setN]?=?useState(1)
          ????return?(
          ????????<div>
          ????????????<button?onClick={()?=>?setN(n?+?1)}>n+1button>

          ????????????<Home?n={n}>Home>
          ????????div>
          ????)
          }

          function?HomePage(props:?{?n:?number?})?{
          ????return?<div>home?{props.n}div>
          }

          但是此時如果切換頁面后再返回home頁面, home頁面的緩存是會失效的。

          其實(shí)是因?yàn)槲覀儗?shí)時監(jiān)控props的變化, 下次重新渲染時會導(dǎo)致props變化, 然后值就會被初始化了, 導(dǎo)致組件也恢復(fù)到了早期的配置, 可是.... 這不就是緩存失敗了嗎?

          每次組件props被重置就會導(dǎo)致組件的相關(guān)數(shù)據(jù)被重置, 嘗試把home組件做如下更改:

          function?HomePage(props:?{?n:?number?})?{
          ????const?[x,?setX]?=?useState(1)
          ????return?(
          ????????<div>
          ????????????<button?onClick={()?=>?setX(x?+?1)}>x?+?1button>

          ????????????<div>home?{props.n}div>
          ????????????<div>home:?x?{x}div>
          ????????div>
          ????)
          }?

          上述寫法會導(dǎo)致每次激活home組件, 只能保留x的值, n的值會與傳入的相同。

          這種變化可能會導(dǎo)致bug, 假設(shè)只有 n > 2 才能讓 x > 3, 此時我們通過點(diǎn)擊事件讓 n = 5 , x = 4了, 此時切換到其他頁面再回來, 就變成了n = 1, x=4, 違背了我們的初始限制條件, 以此類推在真實(shí)復(fù)雜的開發(fā)環(huán)境中此現(xiàn)象會導(dǎo)致各種奇怪的問題。

          十五、認(rèn)知的代價

          上面的場景可以通過開發(fā)人員自己來控制, 理想情況是keep-alive插件只用來處理不需要外界傳參, 以及不會被外界參數(shù)的變化影響的組件, 但這就開始麻煩了。

          這類問題導(dǎo)致開發(fā)者在插件身上要花的學(xué)習(xí)成本提高, 使用成本提高, 并且如果某個組件本來不需要傳參, 我們用keep-alive包裹起來了, 后續(xù)又需要傳參了, 改變的成本想想都麻煩。

          網(wǎng)上現(xiàn)有(2022年04月10日17:16:22)組件的官網(wǎng)基本是沒有認(rèn)真的對用戶講述相關(guān)的問題, 往往都是以介紹"使用方法"與闡述自己的優(yōu)勢為主, 這就導(dǎo)致用戶被莫名其妙的bug折磨。

          傳遞 Provider 的方法也有問題, 需要傳遞可能不是本頁代碼的Provider, 難受的了啊。

          想要解決keep-alive相關(guān)問題的思路可以換一下, 最好是在react源碼里支持一波, 比如可以指定某些組件不被銷毀, 其實(shí)我們可以關(guān)注一下react18的后續(xù)版本, 現(xiàn)在這個時間段react18發(fā)布了正式版。

          十六、如何升級到react18

          方式一: create-react-app 創(chuàng)建新項(xiàng)目

          現(xiàn)階段直接使用下面的命令, 就可創(chuàng)建react18項(xiàng)目:

          npx?create-react-app?my_react
          image.png

          下面這種使用 --template 指定模板的還不行, 因?yàn)槟0宕a還沒更新:

          npx?create-react-app?my_react?--template?typescript

          這里可以查看所有react項(xiàng)目的模板 create-react-app項(xiàng)目可指定的模板。

          方式二: 老項(xiàng)目改裝

          首先直接把依賴?yán)锩娴?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">react 與 react-dom的版本號改成 "^18.0.0"即可。

          兩種方式都需要修改 index.js

          啟動項(xiàng)目會有報錯信息:

          舊版的index.js

          新版的index.js

          image.png

          其他的沒有太多更改了。

          十七、react18 Offscreen 組件的用法

          Offscreen 允許 React 通過隱藏組件而不是卸載組件來保持這樣的狀態(tài), React 將調(diào)用與卸載時相同的生命周期鉤子, 但它也會保留 React 組件和 DOM 元素的狀態(tài)。

          React Activation 中也推薦大家關(guān)注這個屬性:

          image.png

          Offscreen 是什么的官方說法可以看這篇文章里的翻譯: React v18.0新特性官方文檔[中英文對照

          image.png

          Offscreen的測試用例:

          image.png

          遺憾的是 Offscreen 組件并沒有在當(dāng)前版本推出, 其還處于不穩(wěn)定階段, 但我們可以通過 react18 里面的測試用例來預(yù)覽一下其用法:

          image.png

          通過上述寫法還無法看出 Offscreen 到底如何使用, 只知道它可能是以組件的形式出現(xiàn), 并且需要傳入一個mode屬性, 更多用法期待官方盡快推出吧。

          end

          讓我們一起期待 react18 來解決keep-alive這個問題吧, 這次就是這樣, 希望與你一起進(jìn)步。

          關(guān)于本文

          作者:lulu_up

          https://segmentfault.com/a/1190000041683421


          往期推薦


          來自2.5年前端小白的總結(jié)
          95年女程序員內(nèi)心的感受
          邊緣渲染是如何提高前端性能的?

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...

          點(diǎn)個在看支持我吧
          瀏覽 40
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  欧美黄色三级网站 | 韩国一区二区三区四区不卡视频 | 18禁黄色成人网站 | 91人人干| 免费黄色成人网站在线观看 |