在React中實現和Vue同樣溫馨的keep-alive

寫在開頭

  • 在vue中有自然的keep-alive這個功能實現,react也有一個庫,react-keep-alive,可是這個庫是直接進行dom操做,會致使數據驅動失效,斷層
  • 至於爲何會失效斷層,你們能夠看我以前對react-keep-alive這個庫源碼的研究講解,大概一共1500行TS代碼
  • 最近確實比較忙,沒什麼時間寫文章,可是儘可能給你們寫一些實用的東西,也在準備作一個很是棒的東西給你們去學習
  • 如何在React中實現keep-alive?(react-keep-alive源碼講解)
  • [](http://mp.weixin.qq.com/s?__b...: 
  • https://github.com/search?q=react-activation
  • 使用安裝
  • yarn add react-activation

正式開始

  • 什麼是狀態保存?
  • 假設有下述場景:
  • 移動端中,用戶訪問了一個列表頁,上拉瀏覽列表頁的過程當中,隨着滾動高度逐漸增長,數據也將採用觸底分頁加載的形式逐步增長,列表頁瀏覽到某個位置,用戶看到了感興趣的項目,點擊查看其詳情,進入詳情頁,從詳情頁退回列表頁時,須要停留在離開列表頁時的瀏覽位置上
  • 相似的數據或場景還有已填寫但未提交的表單、管理系統中可切換和可關閉的功能標籤等,這類數據隨着用戶交互逐漸變化或增加,這裏理解爲狀態,在交互過程當中,由於某些緣由須要臨時離開交互場景,則須要對狀態進行保存
  • 在 React 中,咱們一般會使用路由去管理不一樣的頁面,而在切換頁面時,路由將會卸載掉未匹配的頁面組件,因此上述列表頁例子中,當用戶從詳情頁退回列表頁時,會回到列表頁頂部,由於列表頁組件被路由卸載後重建了,狀態被丟失

如何實現 React 中的狀態保存

  • 在 Vue 中,咱們能夠很是便捷地經過<keep-alive>標籤實現狀態的保存,該標籤會緩存不活動的組件實例,而不是銷燬它們
  • 而在 React 中並無這個功能,曾經有人在官方提過功能 issues ,但官方認爲這個功能容易形成內存泄露,表示暫時不考慮支持,因此咱們須要本身想辦法了
  • 常見的解決方式:手動保存狀態
  • 手動保存狀態,是比較常見的解決方式,能夠配合 React 組件的 componentWillUnmount 生命週期經過 redux 之類的狀態管理層對數據進行保存,經過 componentDidMount 週期進行數據恢復
  • 在須要保存的狀態較少時,這種方式能夠比較快地實現咱們所需功能,但在數據量大或者狀況多變時,手動保存狀態就會變成一件麻煩事了
  • 做爲程序員,固然是儘量懶啦,爲了避免須要每次都關心如何對數據進行保存恢復,咱們須要研究如何自動保存狀態
  • 最初的版本react-keep-alive

  • 1500行TypeScript代碼在React中實現組件keep-alive 個人這篇文章對源碼進行了解析,可是這個庫存在斷層現象,雖然能夠緩存最後一次狀態渲染結果,可是後面數據變化沒法再進行數據驅動。並且是藉助React.createPortal 藉助實現,我跟下面這個庫的做者都以爲這是多餘的,其實只須要抽取children屬性,再封裝一次HOC高階組件便可。
  • 整體來講,react-keep-alive這個庫比較重,實現原理也不難,就是笨重,斷層,源碼跳來跳去,真的理清楚了就好

react-activation優雅的實現

  • 效果實現:

庖丁解牛,源碼解析

  • 最簡單版本的react中keep-alive實現演示地址
  • 使用方式:開箱即用
import React, { useState } from 'react'  
import { render } from 'react-dom'  
import KeepAlive, { AliveScope } from './KeepAlive'  
  
  
  
function App() {  
  const [show, setShow] = useState(true)  
  return (  
    <div>  
      <button onClick={() => setShow(show => !show)}>Toggle</button>  
      <p>無 KeepAlive</p>  
      {show && <Counter />}  
      <p>有 KeepAlive</p>  
      {show && (  
        <KeepAlive id="Test">  
          <Counter />  
        </KeepAlive>  
      )}  
    </div>  
  )  
}  
  
....  
  
  
render(  
  <AliveScope>  
    <App />  
  </AliveScope>,  
  document.getElementById('root')  
)
  • 注意 :緩存的虛擬DOM元素會儲存在AliveScope 組件中,因此它不能被卸載
  • 使用AliveScope 配合KeepAlive便可達到緩存效果,相似react-keep-alive
  • 首先咱們看看AliveScope 組件作了什麼事情
export class AliveScope extends Component {  
  nodes = {}  
  state = {}  
  
  keep = (id, children) =>  
    new Promise(resolve =>  
      this.setState(  
        {  
          [id]: { id, children }  
        },  
        () => resolve(this.nodes[id])  
      )  
    )  
  
  render() {  
    return (  
      <Provider value={this.keep}>  
        {this.props.children}  
        {Object.values(this.state).map(({ id, children }) => (  
          <div  
            key={id}  
            ref={node => {  
              this.nodes[id] = node  
            }}  
          >  
            {children}  
          </div>  
        ))}  
      </Provider>  
    )  
  }  
}
  • 它的源碼只有幾十行,很簡單,這裏的this.props.children是虛擬DOM,通過Babel編譯和React處理,最終會轉化成真實DOM節點渲染
  • 逐步解析:
{this.props.children}
  • 是這個組件的全部子元素,必需要渲染
  • 使用React的Context API進行傳遞KEEP方法給全部的子孫組件,每次這個方法被調用,都會形成AliveScope 組件從新渲染,進而刷新子組件,而且返回一個真實的DOM節點,這個真實的DOM節點就能夠被直接DOM操做。

  • 這張思惟導圖,能夠很清楚的表示,咱們的緩存實現方式,若是看不懂,慢慢往下看

KeepAlive組件的源碼

import React, { Component, createContext } from 'react'  
  
const { Provider, Consumer } = createContext()  
  
const withScope = WrappedCompoennt => props => (  
  <Consumer>{keep => <WrappedCompoennt {...props} keep={keep} />}</Consumer>  
)  
  
  
  
@withScope  
class KeepAlive extends Component {  
  constructor(props) {  
    super(props)  
    this.init(props)  
  }  
  
  init = async ({ id, children, keep }) => {  
    const realContent = await keep(id, children)  
    this.placeholder.appendChild(realContent)  
  }  
  
  render() {  
    return (  
      <div  
        ref={node => {  
          this.placeholder = node  
        }}  
      />  
    )  
  }  
}  
  
export default KeepAlive
  • withScope是一個高階組件,將KeepAlive組件傳入,返回一個新的組件,這裏使用了裝飾器,@withScope.其實最終export default 的是withScope(KeepAlive)
  • 這裏就是跟react-keep-alive的真正區別,withScope使用了context api捕獲了傳入的虛擬DOM節點,橋接了父組件以及KeepAlive組件的關聯,一旦children屬性改變,那麼withScope被刷新,進而傳入新的children屬性給KeepAlive組件,致使數據驅動能夠進行組件刷新
  • 這又印證了那句話
  • 在計算機的世界裏,若是出現解決不了的問題,那就加一箇中間層,若是還不行就加兩個 --來自不知名碼農Peter

  • 這裏按照代碼運行邏輯,完整的解析了它的簡單緩存機制實現,思路總體比較清晰,加上代碼本身斷點調試難度應該比較低,我的以爲這個庫的設計和思想,都是不錯的,值得推廣,做者也是比較樂意解答問題。你們有問題能夠在github上提問。
相關文章
相關標籤/搜索