現代框架的本質其實仍是
Dom
操做,今天看到一句話特別喜歡,不要給本身設限,到最後,大多數的技術本質是相同的。
例如後端用到的Kafka , redis , sql事務寫入 ,Nginx
負載均衡算法,diff
算法,GRPC
,Pb 協議
的序列化和反序列化,鎖等等,均可以在前端被相似的大量複用邏輯,即使js
和Node.js
都是單線程的前端
認真看完本文與源碼,你會收穫很多東西
框架誰優誰劣,就像Web
技術的開發效率與Native
開發的用戶體驗同樣誰也很差一言而論誰高誰低,不過能夠肯定的是,web
技術已經愈來愈接近Native
端體驗了
做者是一位跨平臺桌面端開發的前端工程師,因爲是即時通信應用,項目性能要求很高。因而苦尋名醫,爲了達到想要的性能,最終選定了很是冷門的幾種優化方案拼湊在一塊兒node
過程雖然很是曲折,可是市面上能用的方案都用到了,嘗試過了,可是後面發現,極致的優化,並非1+1=2
,要考慮業務的場景,由於一旦優化方案多了,他們之間的技術出發點,考慮的點可能會衝突。react
這也是前端須要架構師的緣由,開發重型應用若是前端有了一位架構師,那麼會少走不少彎路。git
後端也是如此github
Vue.js
中的keep-alive
使用:在Vue.js
中,尤大大是這樣定義的:web
keep-alive
主要用於保留組件狀態或避免從新渲染redis
基礎使用:算法
<keep-alive> <component :is="view"></component> </keep-alive>
大概思路:sql
切換也是很是平滑,沒有任何的閃屏
特別提示: 這裏每一個組件,下面還有一個1000行的列表哦~ 切換也是秒級
import {Provider , KeepAlive} from 'react-component-keepalive';
將須要緩存渲染的組件包裹,而且給一個name
屬性便可npm
例如:
import Content from './Content.jsx' export default App extends React.PureComponent{ render(){ return( <div> <Provider> <KeepAlive name="Content"> <Content/> </KeepAlive> </Provider> </div> ) } }
這樣這個組件你就能夠在第二次須要渲染他的時候直接取緩存渲染了
下面是一組被緩存的一個組件,
仔細看上面的註釋內容,再看當前body
中多出來的div
那麼他們是否是對應上了呢? 會是怎樣緩存渲染的呢?
找到庫的源碼入口:
import Provider from './components/Provider'; import KeepAlive from './components/KeepAlive'; import bindLifecycle from './utils/bindLifecycle'; import useKeepAliveEffect from './utils/useKeepAliveEffect'; export { Provider, KeepAlive, bindLifecycle, useKeepAliveEffect, };
最主要先看 Provider,KeepAlive
這兩個組件:
緩存組件這個功能是經過 React.createPortal API
實現了這個效果。
react-component-keepalive
有兩個主要的組件 <Provider>
和 <KeepAlive>
;<Provider>
負責保存組件的緩存,並在處理以前經過 React.createPortal API
將緩存的組件渲染在應用程序的外面。緩存的組件必須放在 <KeepAlive>
中,<KeepAlive>
會把在應用程序外面渲染的組件掛載到真正須要顯示的位置。
這樣很明瞭了,原來如此
開始源碼:
Provider
組件生命週期
public componentDidMount() { //建立`body`的div標籤 this.storeElement = createStoreElement(); this.forceUpdate(); }
createStoreElement
函數其實就是建立一個相似UUID
的附帶註釋內容的div
標籤在body
中
import {prefix} from './createUniqueIdentification'; export default function createStoreElement(): HTMLElement { const keepAliveDOM = document.createElement('div'); keepAliveDOM.dataset.type = prefix; keepAliveDOM.style.display = 'none'; document.body.appendChild(keepAliveDOM); return keepAliveDOM; }
調用createStoreElement
的結果:
而後調用forceUpdate
強制更新一次組件
這個組件內部有大量變量鎖:
export interface ICacheItem { children: React.ReactNode; //自元素節點 keepAlive: boolean; //是否緩存 lifecycle: LIFECYCLE; //枚舉的生命週期名稱 renderElement?: HTMLElement; //渲染的dom節點 activated?: boolean; // 已激活嗎 ifStillActivate?: boolean; //是否一直保持激活 reactivate?: () => void; //從新激活的函數 } export interface ICache { [key: string]: ICacheItem; } export interface IKeepAliveProviderImpl { storeElement: HTMLElement; //剛纔渲染在body中的div節點 cache: ICache; //緩存遵循接口 ICache 一個對象 key-value格式 keys: string[]; //緩存隊列是一個數組,裏面每個key是字符串,一個標識 eventEmitter: any; //這是本身寫的自定義事件觸發模塊 existed: boolean; //是否退出狀態 providerIdentification: string; //提供的識別 setCache: (identification: string, value: ICacheItem) => void; 。//設置緩存 unactivate: (identification: string) => void; //設置不活躍狀態 isExisted: () => boolean; //是否退出,會返回當前組件的Existed的值 }
上面看不懂 別急,看下面:
接着是Provider
組件真正渲染的內容代碼:
<React.Fragment> {innerChildren} { keys.map(identification => { const currentCache = cache[identification]; const { keepAlive, children, lifecycle, } = currentCache; let cacheChildren = children; //中間省略若干細節判斷 return ReactDOM.createPortal( ( cacheChildren ? ( <React.Fragment> <Comment>{identification}</Comment> {cacheChildren} <Comment onLoaded={() => this.startMountingDOM(identification)} >{identification}</Comment> </React.Fragment> ) : null ), storeElement, ); }) } </React.Fragment>
innerChildren
便是傳入給Provider
的children
一開始咱們看見的緩存組件內容顯示的都是一個註釋內容 那爲何能夠渲染出東西來呢
Comment
組件是重點
Comment
組件public render() { return <div />; }
初始返回是一個空的div
標籤
可是看他的生命週期ComponentDidmount
public componentDidMount() { const node = ReactDOM.findDOMNode(this) as Element; const commentNode = this.createComment(); this.commentNode = commentNode; this.currentNode = node; this.parentNode = node.parentNode as Node; this.parentNode.replaceChild(commentNode, node); ReactDOM.unmountComponentAtNode(node); this.props.onLoaded(); }
這個邏輯到這裏並無完,咱們須要進一步查看KeepAlive
組件源碼
KeepAlive
源碼:組件componentDidMount
生命週期鉤子:
public componentDidMount() { const { _container, } = this.props; const { notNeedActivate, identification, eventEmitter, keepAlive, } = _container; notNeedActivate(); const cb = () => { this.mount(); this.listen(); eventEmitter.off([identification, START_MOUNTING_DOM], cb); }; eventEmitter.on([identification, START_MOUNTING_DOM], cb); if (keepAlive) { this.componentDidActivate(); } }
其餘邏輯先無論,重點看:
const cb = () => { this.mount(); this.listen(); eventEmitter.off([identification, START_MOUNTING_DOM], cb); }; eventEmitter.on([identification, START_MOUNTING_DOM], cb);
當接收到事件被觸發後,調用`mout和listen`方法,而後取消監聽這個事件
private mount() { const { _container: { cache, identification, storeElement, setLifecycle, }, } = this.props; this.setMounted(true); const {renderElement} = cache[identification]; setLifecycle(LIFECYCLE.UPDATING); changePositionByComment(identification, renderElement, storeElement); }
changePositionByComment
這個函數是整個調用的重點,下面會解析
private listen() { const { _container: { identification, eventEmitter, }, } = this.props; eventEmitter.on( [identification, COMMAND.CURRENT_UNMOUNT], this.bindUnmount = this.componentWillUnmount.bind(this), ); eventEmitter.on( [identification, COMMAND.CURRENT_UNACTIVATE], this.bindUnactivate = this.componentWillUnactivate.bind(this), ); }
listen
函數監聽的自定義事件爲了觸發componentWillUnmount
和componentWillUnactivate
COMMAND.CURRENT_UNMOUNT
這些都是枚舉而已
changePositionByComment
函數:
export default function changePositionByComment(identification: string, presentParentNode: Node, originalParentNode: Node) { if (!presentParentNode || !originalParentNode) { return; } const elementNodes = findElementsBetweenComments(originalParentNode, identification); const commentNode = findComment(presentParentNode, identification); if (!elementNodes.length || !commentNode) { return; } elementNodes.push(elementNodes[elementNodes.length - 1].nextSibling as Node); elementNodes.unshift(elementNodes[0].previousSibling as Node); // Deleting comment elements when using commet components will result in component uninstallation errors for (let i = elementNodes.length - 1; i >= 0; i--) { presentParentNode.insertBefore(elementNodes[i], commentNode); } originalParentNode.appendChild(commentNode); }
老規矩,上圖解析源碼:
不少人看起來雲裏霧裏,其實最終的實質就是經過了Coment
組件的註釋,來查找到對應的須要渲染真實節點再進行替換,而這些節點都是緩存在內存中,DOM
操做速度遠比框架對比後渲染快。這裏再次獲得體現
這個庫,不管是否路由組件均可以使用, 虛擬列表+緩存KeepAlive組件的Demo體驗地址
庫原連接地址爲了項目安全,我本身重建了倉庫本身定製開發這個庫
感謝原先做者的貢獻 在我出現問題時候也第一時間給了我技術支持 謝謝!
新的庫名叫react-component-keepalive
直接能夠在npm
中找到
npm i react-component-keepalive
就能夠正常使用了
若是你對
React
並不瞭解,能夠看一些我以前的文章:
歡迎關注個人前端公衆號: 前端巔峯
本人專一前端最前沿技術,跨平臺重型應用開發,即時通信等技術。
版本的後續計劃: