首页

React 页面状态数据持久化

React 页面状态数据持久化 - 八云酱

前端开发实战中我们经常会碰到这样的场景:用户填写表单还没有提交之前打开一个其他页面链接(比如用户协议),返回表单页面发现原先填写的所有信息都已经不在需要重新填写一遍。这种体验会严重影响到流程的完整程度,很多潜在用户也会因此流失。

所以我们在写前端代码时需要考虑将页面状态数据缓存下来,等到页面重新打开时进行回显。一个相对比较简单但繁琐的方式就是手写代码将状态数据使用 key => value 保存在本地 storage 里面。

因为个人主要使用 React 开发前端页面,所以这里分享一下自己实现页面状态数据持久化的方案。本方案只是对社区开源组件的简单封装,方便在业务代码开发中直接使用。 笔者将页面状态数据使用 redux 全部存储在全局 store 之中,然后将整个全局 store 持久化下来。

import { createStore, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
import { persistStore, persistReducer } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
import sessionStorage from 'redux-persist/lib/storage/session'
import SparkMD5 from 'spark-md5'

// 是否为正式环境
const isProd = process.env.NODE_ENV !== 'production'

// 获取哈希值
export const getHashMD5 = (data) => {
  const spark = new SparkMD5()
  spark.append(data)
  return spark.end()
}

/**
 * 全局状态缓存对象
 *
 * @param {object} initialState 全局状态字典
 * @returns
 */
const withCache = (initialState) => {
  initialState = Object.assign({}, initialState)


  const setRoot = (state = initialState, action) => {
    switch (action.type) {
      case 'SET_ROOT':
        return { ...state, ...action.payload }
      default:
        return state
    }
  }

  // 哈希缓存键值防止多个页面缓存冲突
  const persistKey = getHashMD5(window.location.pathname)
  const rootReducer = combineReducers({ root: setRoot })
  const persistConfig = { key: persistKey, storage: sessionStorage, debug: !isProd }
  const persistedReducer = persistReducer(persistConfig, rootReducer)

  const store = createStore(
    persistedReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
  const persistor = persistStore(store)

  // 开发环境主动清除缓存数据
  !isProd && persistor.purge()

  // 缓存组件
  const CacheProvider = ({ children }) => (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        {children}
      </PersistGate>
    </Provider>
  )

  const mapStateToProps = (state) => ({
    root: state.root
  })

  const mapDispatchToProps = (dispatch) => ({
    store: persistor,
    setRoot: (payload) => dispatch({ type: 'SET_ROOT', payload })
  })

  // 连接器
  const cacheConnect = (Component) => {
    return connect(mapStateToProps, mapDispatchToProps)(Component)
  }

  return {  CacheProvider, cacheConnect }
}

export default withCache

以上为实际代码封装,所有状态数据都存储在 this.props.root 之中,渲染页面时直接使用即可。更新页面状态时使用 this.props.setRoot({ foo: 'bar' }) 方法,每次数据更新都会直接缓存在 sessionStorage 之中。

全局状态中还有 this.props.store 这个对象,我们可以使用这个对象停止更新或者删除本地缓存数据状态,使用方法参考 redux-persist 文档说明。

简单应用示例

import ReactDOM from 'react-dom'
import MainPage from './MainPage'
import withCache from './withCache'

const initialState = {
  name: '八云酱',
  homepage: 'https://www.bayun.org'
}

const { CacheProvider, cacheConnect } = withCache(initialState)
const CacheConsumer = cacheConnect(MainPage)
const App = (
  <CacheProvider>
    <CacheConsumer />
  </CacheProvider>
)

ReactDOM.render(App, document.querySelector('#app'))

单页应用示例

import ReactDOM from 'react-dom'
import { HashRouter as Router, Route } from 'react-router-dom'
import PageA from './PageA'
import PageB from './PageB'
import withCache from './withCache'

const initialState = {
  name: '八云酱',
  homepage: 'https://www.bayun.org'
}

const { CacheProvider, cacheConnect } = withCache(initialState)
const App = (
  <CacheProvider>
    <Router>
      <Route exact={true} path='/a' component={cacheConnect(PageA)} />
      <Route exact={true} path='/b' component={cacheConnect(PageB)} />
    </Router>
  </CacheProvider>
)

ReactDOM.render(App, document.querySelector('#app'))