React
是近些年出現比較優秀的前端框架,它的設計思想,源碼很是棒。
假設有下述場景:前端
移動端中,用戶訪問了一個列表頁,上拉瀏覽列表頁的過程當中,隨着滾動高度逐漸增長,數據也將採用觸底分頁加載的形式逐步增長,列表頁瀏覽到某個位置,用戶看到了感興趣的項目,點擊查看其詳情,進入詳情頁,從詳情頁退回列表頁時,須要停留在離開列表頁時的瀏覽位置上
相似的數據或場景還有已填寫但未提交的表單、管理系統中可切換和可關閉的功能標籤等,這類數據隨着用戶交互逐漸變化或增加,這裏理解爲狀態,在交互過程當中,由於某些緣由須要臨時離開交互場景,則須要對狀態進行保存node
在 React 中,咱們一般會使用路由去管理不一樣的頁面,而在切換頁面時,路由將會卸載掉未匹配的頁面組件,因此上述列表頁例子中,當用戶從詳情頁退回列表頁時,會回到列表頁頂部,由於列表頁組件被路由卸載後重建了,狀態被丟失react
在 Vue
中,咱們能夠很是便捷地經過 <keep-alive>
標籤實現狀態的保存,該標籤會緩存不活動的組件實例,而不是銷燬它們
而在 React
中並無這個功能,曾經有人在官方提過功能 issues
,但官方認爲這個功能容易形成內存泄露,表示暫時不考慮支持,因此咱們須要本身想辦法了git
手動保存狀態,是比較常見的解決方式,能夠配合 React
組件的 componentWillUnmount
生命週期經過 redux
之類的狀態管理層對數據進行保存,經過 componentDidMount
週期進行數據恢復
在須要保存的狀態較少時,這種方式能夠比較快地實現咱們所需功能,但在數據量大或者狀況多變時,手動保存狀態就會變成一件麻煩事了
做爲程序員,固然是儘量懶啦,爲了避免須要每次都關心如何對數據進行保存恢復,咱們須要研究如何自動保存狀態程序員
react-keep-alive
1500行TypeScript代碼在React中實現組件keep-alive 個人這篇文章對源碼進行了解析,可是這個庫存在斷層現象,雖然能夠緩存最後一次狀態渲染結果,可是後面數據變化沒法再進行數據驅動。並且是藉助React.createPortal
藉助實現,我跟下面這個庫的做者都以爲這是多餘的,其實只須要抽取children
屬性,再封裝一次HOC
高階組件便可。github
整體來講,react-keep-alive
這個庫比較重,實現原理也不難,就是笨重,斷層,源碼跳來跳去,真的理清楚了就好redux
react-activation
優雅的實現效果實現:segmentfault
最簡單版本的react中keep-alive實現演示地址api
使用方式:開箱即用緩存
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
節點渲染
從零本身編寫一個mini-React框架 若是你不是很懂,那麼能夠看個人這篇文章
逐步解析:
{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
上提問。
另外SegmentFault
前端交流羣還有名額,有須要的能夠加我微信:CALASFxiaotan
,裏面大量小姐姐哦
歡迎關注微信公衆號:前端巔峯
以爲不錯記得點個贊哦~ 之後會有更多的源碼解析