「react緩存頁面」從需求到開源(我是怎麼樣讓產品小姐姐另眼相看的)

一 一切根源都從產品小姐姐無厘頭需求開始

最近在開發業務項目的時候,產品小姐姐忽然來到我身邊,而後就對着電腦一頓操做,具體場景大體是這樣的。前端

場景一:vue

如上圖所示,當在數萬級別的數據中,選擇一條,點擊查看,跳轉到當前數據的詳情頁,當點擊按鈕返回返回來,或者是瀏覽器前進後退等其餘操做,返回到列表頁的時候。要記錄當前列表的位置。也就是要還原點擊查看查看前的頁面。可是當點擊tab菜單按鈕的時候,要清除頁面信息。node

場景二:react

如上圖所示,當咱們編輯內容的時候,一些數據可能從其餘頁面得到,因此要求,不管切換路由,切換頁面,當前頁面的編輯信息均不能被置空,只有點擊肯定重置,表單才內容置空。git

場景三: 場景一 + 場景二 是更復雜的緩存頁面信息場景。github

二 梳理需求

接這個需求的時候,咋眼一看,what ,好像是 vue 中的 keepalive + vue router功能,可是,咱們幾個項目技術棧是react ,react , react! react 中沒有對應的 keepalive內置 api,後來上GitHub上搜索相關項目,感受有不少不符合業務需求的狀況。還有一些潛在的風險。 瞬間慌了~~~。心裏有一種萬隻神獸奔騰的感受。算法

在漂亮產品小姐姐面前,怎麼能說不,那不顯得研發能力差,強行裝了一波說很簡單,只能硬着頭皮接下來了。產品小姐姐臨走前還說還鬼魅的笑了笑,說能夠把幾個項目的部分頁面都加上這種效果。vue-router

1 解決方案

1 數據狀態緩存到公共管理可行性

這個需求首先讓我想到的是用redux或者是mobx來把頁面的狀態緩存起來,而後切換頁面的時候,把這些數據緩存進去,再次切換回來的時候,將數據取出來,這樣就一個問題,即使能緩存state層,可是若是一些表單組件是非受控組件,是沒法緩存下來的,還有一些dom狀態是緩存不了的,好比手動添加的一些樣式等。還有就是實際狀況比較複雜,有富文本組件,你是沒法直接獲取綁定的state的。npm

第二個緣由就是有好幾個項目,並且頁面比較多,若是都創建數據管理,那麼工做量會很是的大。 因此數據狀態緩存的可行性不高,即使能夠實現,也須要大量的複製粘貼,這不是咱們的追求。json

2 react-keepalive-router誕生

因此咱們只能選擇本身開發一個項目,而後把它開源,並應用在公司項目中來。既然選擇緩存頁面,那麼爲何不在react-router中的 Route組件和Switch組件中作文章呢,咱們須要對RouteSwitch 組件作一些功能性的拓展,正好筆者以前本身研究過react-router源碼,並寫了一篇(這一次完全弄懂react-router路由原理)[juejin.cn/post/688629…] ,感興趣的同窗能夠三連一波,由於項目是在router路由層面,因此給它起了一個名字react-keepalive-router。接下來就要對整個項目作一個系統的設計。

三設計階段

1 瞭解react-fiber

爲何咱們的項目要提到react-fiber呢,這裏我先說一下,react-fiberReact Fiber 是從 v16 版本開始對 Stack Reconciler 進行的重寫,是 v16 版本的核心算法實現。react在初始化構建過程當中,會產生一個由child指向子fiber,sibling指向兄弟fiber,return指向父fiber三個指針構建的fiber樹結構,裏面保存着dom信息,update信息,props信息等,咱們核心思想就是,在切換頁面的時候,組件銷燬,可是做爲渲染調度的react fiber保存keepalive狀態。只要fiber存活,就能獲取到dom元素,數據層state等信息。

2 基於 react-router-dom 和 react 16.8

首先咱們須要對react-router庫中的 Route組件和Switch組件做出改造,能夠經過路由層面實現緩存路由功能。由於在設計之初,我就想着將用不一樣的狀態管理keepalive狀態,這樣的好處是,後續能夠給緩存路由組件,增長一些額外的聲明週期,好比說vueactivateddeactivated同樣。由於設計思想是狀態管理,項目依賴中不想引入redux等第三方庫,因此這裏選了react-hooksuseReducer恰到好處。這就是react基礎庫 16.8+的緣由之一。另一個緣由就是hooks中有useMemo這樣防止渲染穿透的api,有助於調節路由組件的更新次數。

工做流程分析

受到react-router-cache-route開源項目的啓發,我在設計整個流程的時候,採起了交換dom的方式。

初始化 : 總體設計思路第一次切入緩存頁面的時候,會自動生成一個容器組件,緩存Route會把組件,交給容器組件來掛載,而後容器組件生成fiber,render以後生成對應的dom樹,將dom樹交給Route組件(也就是咱們的正常的頁面)。

切換頁面: 切換頁面的時候,路由組件是確定卸載的,這時候須要將咱們的dom還給容器組件,而後容器組件進入凍結狀態。

再次切換到緩存頁面:再次進入路由頁面的時候,首先從容器中,發現有該頁面的緩存,那麼將容器解封狀態,而後將dom樹,還給當前路由頁面。完成keepalive狀態。

緩存銷燬:: 項目支持銷燬緩存功能,調用銷燬方法,會卸載當前緩存容器,進一步銷燬fiberdom ,完成整個銷燬功能。

工做流程圖

工做原理圖

設計的優點在哪裏?

設計優點:

1 由於內部運用了 useReducer 狀態管理,管理緩存狀態,能夠更靈活,操縱緩存路由組件,採用react hooks全新api,渲染節流,手動解除緩存,增長了緩存的狀態週期,監聽函數等。

2 這套緩存頁面的思想,不只僅能夠用在路由頁面級別,後期能夠遷移的component組件級別上來。也是後續維護和開發的方向。

四 使用簡介 + 快速上手

咱們開始設計項目的用法,api,已經應用場景。經過上述工做原理,講述了 keepliveRouteSwitchkeepliveRoute 在整個緩存過程當中的做用,

下載

由於咱們是把項目上傳到了npm方便其餘項目用,因此能夠直接從 npm 上下載。

npm install react-keepalive-router --save
# or
yarn add react-keepalive-router
複製代碼

使用

1 基本用法

KeepaliveRouterSwitch

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>

複製代碼

例子二

或者咱們可使用 renderRoutesapi配合 KeepliveRouterSwitch 使用 。

import {renderRoutes} from "react-router-config"
<KeepliveRouterSwitch withoutRoute  >{ renderRoutes(routes) }</KeepliveRouterSwitch> 
複製代碼

KeepaliveRoute

KeepaliveRoute 基本使用和 Route沒有任何區別。

在當前版本中⚠️⚠️⚠️若是 KeepaliveRoute 若是沒有被 KeepaliveRouterSwitch包裹就會失去緩存做用。

效果

2 其餘功能

1 緩存組件激活監聽器

若是咱們但願對當前激活的組件,有一些額外的操做,咱們能夠添加監聽器,用來監聽緩存組件的激活狀態。

addKeeperListener((history,cacheKey)=>{
  if(history)console.log('當前激活狀態緩存組件:'+ cacheKey )
})
複製代碼

第一個參數未history對象,第二個參數爲當前緩存路由的惟一標識cacheKey

2 清除緩存

緩存的組件,或是被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:

接下來就是驗證階段首先咱們看一下產品小姐姐第一個需求:

第二個需求:

完美實現產品需求。

六 打包階段 + 發佈npm階段

rollup打包

接下來就是 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

第一步:須要在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.jsonlib 下打包的文件以外,大部分文件是開發時候或者編譯階段用到的,不須要上傳到npm,因此須要在 .npmignore 這麼寫

docs
node_modules
src
md
.babelrc
.gitignore
.npmignore
.prettierrc
rollup.config.js
yarn.lock
複製代碼

七 總結

項目地址

react-keepalive-router

從需求到開源的流程跑通以後,會有很大的成就感,剛開始獨立開發的項目確定頗有不少bug,不怕有bug,要有一顆敢於修復bug並把項目維護下去的決心。

送人玫瑰,手留餘香,閱讀的朋友能夠給筆者**點贊,關注一波。**陸續更新前端文章。

感受有用的朋友能夠關注筆者公衆號 前端Sharing 持續更新前端文章。

喜歡筆者的能夠給筆者投票,海報以下🙏🙏🙏感謝

相關文章
相關標籤/搜索