使用react-activation實現keepAlive支持返回傳參

介紹

這個項目是一個商城的後臺管理系統,用umi2.0搭建,狀態管理使用dva,想要實現類似vue keep-alive的效果。

具體表現為:

從列表頁A跳轉A的詳情頁,列表頁A緩存

  • 詳情頁沒做任何操作,跳回列表頁A,列表頁A不刷新,列表頁A頁碼不變
  • 詳情頁進行瞭編輯操作,跳回列表頁A,列表頁A刷新,列表頁A頁碼不變
  • 詳情頁進行瞭新建操作,跳回列表頁A,列表頁A刷新,列表頁A頁碼變為1

從列表頁A跳轉列表頁B,列表頁A不緩存

總結就是,一個頁面隻有跳轉指定頁面的時候才緩存,並且當返回這個被緩存的頁面時,可以控制是否刷新。

代碼

1、安裝react-activation

"react-activation": "^0.10.2",

2、給路由增加meta

這個項目使用的是集中式配置路由,我增加瞭meta屬性,meta.keepAlive存在表示這是一個需要被keepAlive的路由,meta.keepAlive.toPath表示隻有當前往這個路由的時候,需要緩存

const routes = [
    ...
    {
        name: '商品管理(商城商品)', 
        path: '/web/supplier/goods/mallgoodsmgr',
        component: './supplier/goods/goodsManage',
        meta: {
          keepAlive: {
            toPath: '/web/supplier/goods/mallgoodsmgr/detail', // 隻有去詳情頁的時候 才需要緩存 商品管理(商城商品)這個路由
          },
        },
    }
    ...
]

3、根組件中渲染

在根組件中,用<AliveScope/>包裹整個應用,用<KeepAlive/>包裹需要緩存的頁面。文檔中這部分寫在<App/>中,如果是umi可以寫在layouts裡。
通過tree的扁平化計算獲取全部的帶有meta.keepAlive的routes:keepAliveRoutes,通過location.pathname判斷,如果當前頁面是需要keepAlive的,那麼就需要用<KeepAlive/>包裹。

import KeepAlive, { AliveScope, useAliveController } from 'react-activation'

// tree扁平化
function treeToList(tree, childrenKey = 'routes') {
  var queen = []
  var out = []
  queen = queen.concat(tree)
  while (queen.length) {
    var first = queen.shift()
    if (first[childrenKey]) {
      queen = queen.concat(first[childrenKey])
      delete first[childrenKey]
    }
    out.push(first)
  }
  return out
}

// 從routes路由tree裡,拿到所有meta.keepAlive的路由:keepAliveRoutes
const allFlatRoutes = treeToList(routes) // 所有路由
const keepAliveRoutes = allFlatRoutes.filter((item) => item.meta?.keepAlive) // keepAlive的路由

function Index(props) {
  const location = useLocation()
  
  const routeItem = keepAliveRoutes.find(
    (item) => item.path == location.pathname
  ) // from 頁面

  let dom = props.children
  if (routeItem) {
    dom = <KeepAlive id={location.pathname}>{props.children}</KeepAlive> // id 一定要加 否則 keepAlive的頁面 跳轉 另一個keepAlive的頁面 會有問題
  }

  return (
    <AliveScope>
      <div className={styles.page_container}>{dom}</div>
    </AliveScope>
  )
}

註意AliveScope中包含多個KeepAlive的話,<KeepAlive/>一定要帶id。

4、跳轉指定頁面的時候才緩存

上一步之後,頁面雖然被緩存,但是它跳轉任何頁面都會緩存,我們需要隻有跳轉指定頁面的時候才緩存。
我的方法是

如果跳轉的頁面正好是它自己的meta.keepAlive.toPath,那就不做任何操作(因為此時本頁面已經被KeepAlive包裹瞭,處於緩存的狀態)
如果不是它自己的meta.keepAlive.toPath,調用clear方法,清空緩存

4.1 clear方法

react-activation提供useAliveController可以手動控制緩存,其中clear方法用於清空所有緩存中的 KeepAlive

4.2 用狀態管理記錄toPath

監聽history,用狀態管理(我用的dva)記錄即將前往的頁面(下一個頁面)toPath
我通過dva記錄應用即將前往的頁面

const GlobalModel = {
  namespace: 'global',

  state: {
    /**
     * keepAlive
     */
    toPath: '',
    keepAliveOptions: {}, // 給keepAlive的頁面 傳的options
  },

  effects: {},

  reducers: {
    save(state, { payload }) {
      return {
        ...state,
        ...payload,
      }
    },
    setToPath(state, { payload }) {
      return {
        ...state,
        toPath: payload,
      }
    },
  },

  subscriptions: {
    setup({ history, dispatch }) {
      // Subscribe history(url) change, trigger `load` action if pathname is `/`
      history.listen((route, typeStr) => {
        const { pathname } = route
        dispatch({
          type: 'setToPath',
          payload: pathname,
        })
      })
    },
  },
}

4.3 給根組件增加useEffect
根組件從dva中讀取即將訪問的頁面toPath,然後加一個useEffect,如果即將前往的頁面不是當前路由自己的meta.keepAlive.toPath,就執行react-activation提供的clear方法

...

function Index(props) {
  const location = useLocation()
  const toPath = props.global.toPath // 從dva中拿到 將要訪問的頁面
  
  const routeItem = keepAliveRoutes.find(
    (item) => item.path == location.pathname
  ) // from 頁面
  
  
  /// 新加代碼
  /// 新加代碼
  /// 新加代碼
  useEffect(() => {
    console.log('toPath改變', toPath)

    // from頁面 是需要keepAlive的頁面
    if (routeItem) {
      console.log('from頁面 是需要keepAlive的頁面', routeItem)
      if (toPath == routeItem.meta?.keepAlive.toPath) {
        // 所去的 頁面 正好是當前這個路由的 keepAlive.toPath
        console.log('所去的 頁面 正好是當前這個路由的 keepAlive.toPath,不做什麼')
      } else {
        console.log('clear')
        if (aliveController?.clear) {
          aliveController.clear()
        }
      }
    }
  }, [toPath])
  /// 新加代碼 end

  let dom = props.children
  if (routeItem) {
    dom = <KeepAlive id={location.pathname}>{props.children}</KeepAlive> // id 一定要加 否則 keepAlive的頁面 跳轉 另一個keepAlive的頁面 會有問題
  }

  return (
    <AliveScope>
      <div className={styles.page_container}>{dom}</div>
    </AliveScope>
  )
}
export default connect(({ global, login }) => ({ global, login }))(Index)

4.4 優化

現在有一個問題:從列表A跳轉詳情頁,然後跳轉列表B,再跳轉列表A的時候,A是不刷新的:
列表A => 詳情頁 => 列表B => 列表A 此時列表A不刷新或者空白。
因為從詳情頁出來(跳轉列表B)的時候,我們沒有清空列表A的緩存。
所以要檢查當前頁面是否是某個需要keepAlive頁面的toPath頁面

根組件:

function Index(){
  ...
  
  const parentItem = keepAliveRoutes.find((item) => item.meta?.keepAlive?.toPath == location.pathname) // parentItem存在表示 當前頁面 是某個keepAlive的頁面 的toPath

  useEffect(() => {
    console.log('toPath改變', toPath)

    ...
    
    /// 新加代碼
    /// 新加代碼
    /// 新加代碼
    // from頁面 是某個keepAlive的頁面 的toPath
    if (parentItem) {
      console.log('from頁面 是某個keepAlive的頁面 的toPath,parentItem', parentItem)
      if (toPath == parentItem.path) {
        // 所去的 頁面是 parentItem.path
        console.log('所去的 頁面是 parentItem.path,不做什麼')
      } else {
        console.log('clear')
        if (aliveController?.clear) {
          aliveController.clear()
        }
      }
    }
  }, [toPath])
  
  ...
}

5、抽離邏輯到自定義hooks

useKeepAliveLayout.js

import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import KeepAlive, { AliveScope, useAliveController } from 'react-activation'
import routes from '../../config/router.config'

// tree扁平化
function treeToList(tree, childrenKey = 'routes') {
  var queen = []
  var out = []
  queen = queen.concat(tree)
  while (queen.length) {
    var first = queen.shift()
    if (first[childrenKey]) {
      queen = queen.concat(first[childrenKey])
      delete first[childrenKey]
    }
    out.push(first)
  }
  return out
}

const allFlatRoutes = treeToList(routes) // 所有路由
const keepAliveRoutes = allFlatRoutes.filter((item) => item.meta?.keepAlive) // keepAlive的路由

function index(props) {
  const location = useLocation()

  // keep alive
  const aliveController = useAliveController()

  const toPath = props.global.toPath // 將要訪問的頁面
  const routeItem = keepAliveRoutes.find((item) => item.path == location.pathname) // from 頁面
  const parentItem = keepAliveRoutes.find((item) => item.meta?.keepAlive?.toPath == location.pathname)

  useEffect(() => {
    console.log('toPath改變', toPath)

    // from頁面 是需要keepAlive的頁面
    if (routeItem) {
      console.log('from頁面 是需要keepAlive的頁面', routeItem)
      if (toPath == routeItem.meta?.keepAlive.toPath) {
        // 所去的 頁面 正好是當前這個路由的 keepAlive.toPath
        console.log('所去的 頁面 正好是當前這個路由的 keepAlive.toPath,不做什麼')
      } else {
        console.log('clear')
        if (aliveController?.clear) {
          aliveController.clear()
        }
      }
    }

    // from頁面 是某個keepAlive的頁面 的toPath
    if (parentItem) {
      console.log('from頁面 是某個keepAlive的頁面 的toPath,parentItem', parentItem)
      if (toPath == parentItem.path) {
        // 所去的 頁面是 parentItem.path
        console.log('所去的 頁面是 parentItem.path,不做什麼')
      } else {
        console.log('clear')
        if (aliveController?.clear) {
          aliveController.clear()
        }
      }
    }
  }, [toPath])

  return {
    fromIsNeedKeepAlive: routeItem,
  }
}

export default index

根組件隻需要引入這個hooks就可以瞭:

function Index(props) {
  const location = useLocation()

  const { fromIsNeedKeepAlive } = useKeepAliveLayout(props) // 關鍵代碼關鍵代碼關鍵代碼

  let dom = props.children
  if (fromIsNeedKeepAlive) {
    dom = <KeepAlive id={location.pathname}>{props.children}</KeepAlive> // id 一定要加 否則 keepAlive的頁面 跳轉 另一個keepAlive的頁面 會有問題
  }

  return (
    <AliveScope>
      <div className={styles.page_container}>{dom}</div>
    </AliveScope>
  )
}

6、 從詳情頁返回列表頁的時候,控制列表頁是否刷新,即返回傳參

現在隻剩下這最後一個問題瞭,其實就是keepAlive的頁面,goBack傳參的問題

思路:

  • 狀態管理中增加一個keepAliveOptions對象,這就是詳情頁給列表頁傳的參數
  • 詳情頁執行goBack的時候,調用狀態管理dispatch修改keepAliveOptions
  • 列表頁監聽keepAliveOptions,如果keepAliveOptions改變就執行傳入的方法

useKeepAliveOptions.js

import { useEffect } from 'react'
import { useDispatch, useStore } from 'dva'
import { router } from 'umi'

/**
 * @description keepAlive的頁面,當有參數傳過來的時候,可以用這個監聽到
 * @param {(options:object)=>void} func
 */
export function useKeepAlivePageShow(func) {
  const dispatch = useDispatch()
  const store = useStore()
  const state = store.getState()
  const options = state.global.keepAliveOptions ?? {}

  useEffect(() => {
    func(options) // 執行
    return () => {
      console.log('keepAlive頁面 的緩存 卸載')
      dispatch({
        type: 'global/save',
        payload: {
          keepAliveOptions: {},
        },
      })
    }
  }, [JSON.stringify(options)])
}

/**
 * @description PageA(keepAlive的頁面)去瞭 PageB, 當從PageB goBack,想要給PageA傳參的時候,需要使用這個方法
 * @returns {(params:object)=>void}
 */
export function useKeepAliveGoback() {
  const dispatch = useDispatch()

  function goBack(parmas = {}) {
    dispatch({
      type: 'global/save',
      payload: {
        keepAliveOptions: parmas,
      },
    })
    router.goBack()
  }

  return goBack
}

使用:

詳情頁

import { useKeepAliveGoback } from '@/hooks/useKeepAliveOptions'

function Index(){
    ...
    const keepAliveGoback = useKeepAliveGoback() // 用於給上一頁keepAlive的頁面 傳參
    ...
    
    return (
        <>
            ...
            <button onClick={() => {
                keepAliveGoback({ isAddSuccess: true }) // 給列表頁傳options
            }></button>
            ...
        </>
    )
}

列表頁

import { useKeepAlivePageShow } from '@/hooks/useKeepAliveOptions'

function Index(){
    ...
    // options: isAddSuccess isEditSuccess
    useKeepAlivePageShow((options) => {
        console.log('keepAlive options', options)
        if (options.isAddSuccess) {
          // 新建成功 // 列表頁碼變為1 並且刷新
          search()
        } else if (options.isEditSuccess) {
          // 編輯成功 // 列表頁碼不變 並且刷新
          getData()
        }
    })
    
    ...
    
    return <>...</>
}

相關文檔

react-activation
dva文檔

到此這篇關於使用react-activation實現keepAlive支持返回傳參的文章就介紹到這瞭,更多相關react-activation keepAlive返回傳參內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: