最近在開發業務項目的時候,產品小姐姐忽然來到我身邊,而後就對着電腦一頓操做,具體場景大體是這樣的。前端
場景一:vue
如上圖所示,當在數萬級別的數據中,選擇一條,點擊查看,跳轉到當前數據的詳情頁,當點擊按鈕返回返回來,或者是瀏覽器前進後退等其餘操做,返回到列表頁的時候。要記錄當前列表的位置。也就是要還原點擊查看查看前的頁面。可是當點擊tab菜單按鈕的時候,要清除頁面信息。node
場景二:react
如上圖所示,當咱們編輯內容的時候,一些數據可能從其餘頁面得到,因此要求,不管切換路由,切換頁面,當前頁面的編輯信息均不能被置空,只有點擊肯定 ,重置,表單才內容置空。git
場景三: 場景一 + 場景二 是更復雜的緩存頁面信息場景。github
接這個需求的時候,咋眼一看,what ,好像是 vue
中的 keepalive
+ vue router
功能,可是,咱們幾個項目技術棧是react
,react
, react
! react
中沒有對應的 keepalive
內置 api
,後來上GitHub
上搜索相關項目,感受有不少不符合業務需求的狀況。還有一些潛在的風險。 瞬間慌了~~~。心裏有一種萬隻神獸奔騰的感受。算法
在漂亮產品小姐姐面前,怎麼能說不,那不顯得研發能力差,強行裝了一波說很簡單,只能硬着頭皮接下來了。產品小姐姐臨走前還說還鬼魅的笑了笑,說能夠把幾個項目的部分頁面都加上這種效果。vue-router
這個需求首先讓我想到的是用redux
或者是mobx
來把頁面的狀態緩存起來,而後切換頁面的時候,把這些數據緩存進去,再次切換回來的時候,將數據取出來,這樣就一個問題,即使能緩存state
層,可是若是一些表單組件是非受控組件,是沒法緩存下來的,還有一些dom
狀態是緩存不了的,好比手動添加的一些樣式等。還有就是實際狀況比較複雜,有富文本組件,你是沒法直接獲取綁定的state
的。npm
第二個緣由就是有好幾個項目,並且頁面比較多,若是都創建數據管理,那麼工做量會很是的大。 因此數據狀態緩存的可行性不高,即使能夠實現,也須要大量的複製粘貼,這不是咱們的追求。json
因此咱們只能選擇本身開發一個項目,而後把它開源,並應用在公司項目中來。既然選擇緩存頁面,那麼爲何不在react-router
中的 Route
組件和Switch
組件中作文章呢,咱們須要對Route
和 Switch
組件作一些功能性的拓展,正好筆者以前本身研究過react-router
源碼,並寫了一篇(這一次完全弄懂react-router路由原理)[juejin.cn/post/688629…] ,感興趣的同窗能夠三連一波,由於項目是在router
路由層面,因此給它起了一個名字react-keepalive-router
。接下來就要對整個項目作一個系統的設計。
爲何咱們的項目要提到react-fiber
呢,這裏我先說一下,react-fiber
, React Fiber
是從 v16 版本開始對 Stack Reconciler
進行的重寫,是 v16 版本的核心算法實現。react
在初始化構建過程當中,會產生一個由child
指向子fiber
,sibling
指向兄弟fiber
,return
指向父fiber
三個指針構建的fiber
樹結構,裏面保存着dom
信息,update
信息,props
信息等,咱們核心思想就是,在切換頁面的時候,組件銷燬,可是做爲渲染調度的react fiber
保存keepalive
狀態。只要fiber
存活,就能獲取到dom
元素,數據層state
等信息。
首先咱們須要對react-router
庫中的 Route
組件和Switch
組件做出改造,能夠經過路由層面實現緩存路由功能。由於在設計之初,我就想着將用不一樣的狀態管理keepalive
狀態,這樣的好處是,後續能夠給緩存路由組件,增長一些額外的聲明週期,好比說vue
中 activated
和 deactivated
同樣。由於設計思想是狀態管理,項目依賴中不想引入redux
等第三方庫,因此這裏選了react-hooks
中 useReducer
恰到好處。這就是react
基礎庫 16.8+
的緣由之一。另一個緣由就是hooks
中有useMemo
這樣防止渲染穿透的api,有助於調節路由組件的更新次數。
受到react-router-cache-route
開源項目的啓發,我在設計整個流程的時候,採起了交換dom
樹的方式。
初始化 : 總體設計思路第一次切入緩存頁面的時候,會自動生成一個容器組件,緩存Route
會把組件,交給容器組件來掛載,而後容器組件生成fiber
,render
以後生成對應的dom
樹,將dom
樹交給Route
組件(也就是咱們的正常的頁面)。
切換頁面: 切換頁面的時候,路由組件是確定卸載的,這時候須要將咱們的dom
還給容器組件,而後容器組件進入凍結狀態。
再次切換到緩存頁面:再次進入路由頁面的時候,首先從容器中,發現有該頁面的緩存,那麼將容器解封狀態,而後將dom
樹,還給當前路由頁面。完成keepalive
狀態。
緩存銷燬:: 項目支持銷燬緩存功能,調用銷燬方法,會卸載當前緩存容器,進一步銷燬fiber
和 dom
,完成整個銷燬功能。
設計優點:
1 由於內部運用了 useReducer
狀態管理,管理緩存狀態,能夠更靈活,操縱緩存路由組件,採用react hooks
全新api
,渲染節流,手動解除緩存,增長了緩存的狀態週期,監聽函數等。
2 這套緩存頁面的思想,不只僅能夠用在路由頁面級別,後期能夠遷移的component
組件級別上來。也是後續維護和開發的方向。
咱們開始設計項目的用法,api,已經應用場景。經過上述工做原理,講述了 keepliveRouteSwitch
和 keepliveRoute
在整個緩存過程當中的做用,
由於咱們是把項目上傳到了npm
方便其餘項目用,因此能夠直接從 npm
上下載。
npm install react-keepalive-router --save
# or
yarn add react-keepalive-router
複製代碼
KeepaliveRouterSwitch
能夠理解爲常規的Switch,也能夠理解爲 keepaliveScope
,咱們確保整個緩存做用域,只有一個 KeepaliveRouterSwitch
就能夠了。
import { BrowserRouter as Router, Route, Redirect ,useHistory } from 'react-router-dom'
import { KeepaliveRouterSwitch ,KeepaliveRoute ,addKeeperListener } from 'react-keepalive-router'
const index = () => {
useEffect(()=>{
/* 增長緩存監聽器 */
addKeeperListener((history,cacheKey)=>{
if(history)console.log('當前激活狀態緩存組件:'+ cacheKey )
})
},[])
return <div > <div > <Router > <Meuns/> <KeepaliveRouterSwitch> <Route path={'/index'} component={Index} ></Route> <Route path={'/list'} component={List} ></Route> { /* 咱們將詳情頁加入緩存 */ } <KeepaliveRoute path={'/detail'} component={ Detail } ></KeepaliveRoute> <Redirect from='/*' to='/index' /> </KeepaliveRouterSwitch> </Router> </div> </div>
}
複製代碼
這裏應該注意⚠️的是對於複雜的路由結構。或者KeepaliveRouterSwitch 包裹的子組件不是Route ,咱們要給 KeepaliveRouterSwitch
增長特有的屬性 withoutRoute
就能夠了。以下例子🌰🌰🌰:
例子一
<KeepaliveRouterSwitch withoutRoute >
<div> <Route path="/a" component={ComponentA} /> <Route path="/b" component={ComponentB} /> <KeepaliveRoute path={'/detail'} component={ Detail } ></KeepaliveRoute> </div>
</KeepaliveRouterSwitch>
複製代碼
例子二
或者咱們可使用 renderRoutes
等api
配合 KeepliveRouterSwitch
使用 。
import {renderRoutes} from "react-router-config"
<KeepliveRouterSwitch withoutRoute >{ renderRoutes(routes) }</KeepliveRouterSwitch>
複製代碼
KeepaliveRoute
基本使用和 Route
沒有任何區別。
在當前版本中⚠️⚠️⚠️若是 KeepaliveRoute
若是沒有被 KeepaliveRouterSwitch
包裹就會失去緩存做用。
效果
若是咱們但願對當前激活的組件,有一些額外的操做,咱們能夠添加監聽器,用來監聽緩存組件的激活狀態。
addKeeperListener((history,cacheKey)=>{
if(history)console.log('當前激活狀態緩存組件:'+ cacheKey )
})
複製代碼
第一個參數未history對象,第二個參數爲當前緩存路由的惟一標識cacheKey
緩存的組件,或是被route
包裹的組件,會在props
增長額外的方法cacheDispatch
用來清除緩存。
若是props沒有cacheDispatch
方法,能夠經過
import React from 'react'
import { useCacheDispatch } from 'react-keepalive-router'
function index(){
const cacheDispatch = useCacheDispatch()
return <div>我是首頁 <button onClick={()=> cacheDispatch({ type:'reset' }) } >清除緩存</button> </div>
}
export default index
複製代碼
1 清除全部緩存
cacheDispatch({ type:'reset' })
複製代碼
2 清除單個緩存
cacheDispatch({ type:'reset',payload:'cacheId' })
複製代碼
3 清除多個緩存
cacheDispatch({ type:'reset',payload:['cacheId1','cacheId2'] })
複製代碼
因爲這裏使用公司項目不是很合適,我用了一個本身的項目作demo
:
接下來就是驗證階段首先咱們看一下產品小姐姐第一個需求:
第二個需求:
完美實現產品需求。
接下來就是 rollup
打包階段,rollup
打包階段。項目結構是這樣的。
rollup.config.js
是整個rollup
的配置文件,而後咱們經過 rollup
打包後的文件存在 lib
文件夾下。
rollup.config.js
內容以下
import resolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import { uglify } from 'rollup-plugin-uglify'
export default [
{
input: 'src/index.js',
output: {
name: 'keepaliveRouter',
file: 'lib/index.js',
format: 'cjs',
sourcemap: true
},
external: [
'react',
'react-router-dom',
'invariant'
],
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
})
]
},
/* 壓縮` */
{
input: 'src/index.js',
output: {
name: 'keepaliveRouter',
file: 'lib/index.min.js',
format: 'umd'
},
external: [
'react',
'react-router-dom',
'invariant'
],
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
}),
uglify()
]
}
]
複製代碼
對於發佈npm
第一步:須要在npm
註冊帳號。https://www.npmjs.com/signup
第二步: 登錄 npm login
第三步:建立 package.json
{
"name": "react-keepalive-router", /* 名稱 */
"version": "1.1.0", /* 版本號 */
"description": "基於`react 16.8+` ,`react-router 4+` 開發的`react`緩存組件,能夠用於緩存頁面組件,相似`vue`的`keepalive`包裹`vue-router`的效果功能。", /* 描述 */
"main": "index.js", /* 入口文件 */
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup --config"
},
"keywords": [ /* npm 關鍵詞 */
"keep alive",
"react",
"react router",
"react keep alive route",
"react hooks"
],
"homepage": "https://github.com/GoodLuckAlien/react-keepalive-router", /* 指向 github */
"peerDependencies": { /* npm 項目依賴 */
"react": ">=16.8",
"react-router-dom": ">=4",
"invariant": ">=2"
},
"author": "alien",
"license": "ISC",
"devDependencies": { /* 開發環境下依賴 */
"@babel/core": "^7.12.3",
"@babel/preset-react": "^7.12.5",
"@babel/preset-env": "^7.12.1",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"rollup": "^2.33.3",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-uglify": "^6.0.4"
},
"dependencies": { /* 生產環境依賴 */
"invariant": "^2.2.4"
}
}
複製代碼
第四步:萬事俱備以後,用 npm publish
發佈。
第五步:升級版本,升級版本很簡單,須要咱們在package.json
升級版本號,而後從新 npm publish
就能夠了。
廢棄版本號
若是咱們想廢棄某個版本 , 執行命令 npm deprecate <pkg>[@<version>] <message>
廢棄包
若是咱們想廢棄包 npm unpublish <pkg> --force
.npmignore
.npmignore
裏面聲明的文件和文件價名稱,不會被上傳到 npm
, 個人項目除了 README.md
,package.json
和 lib
下打包的文件以外,大部分文件是開發時候或者編譯階段用到的,不須要上傳到npm
,因此須要在 .npmignore
這麼寫
docs
node_modules
src
md
.babelrc
.gitignore
.npmignore
.prettierrc
rollup.config.js
yarn.lock
複製代碼
從需求到開源的流程跑通以後,會有很大的成就感,剛開始獨立開發的項目確定頗有不少bug
,不怕有bug
,要有一顆敢於修復bug
並把項目維護下去的決心。
送人玫瑰,手留餘香,閱讀的朋友能夠給筆者**點贊,關注一波。**陸續更新前端文章。
感受有用的朋友能夠關注筆者公衆號 前端Sharing 持續更新前端文章。
喜歡筆者的能夠給筆者投票,海報以下🙏🙏🙏感謝