发表于

Redux for components cache and reuse

作者

In project development, in order to avoid memory leaks, it is usually necessary to destroy the map instance when the component is unloaded. However, if users frequently enter and leave the map page, reloading the map each time will cause unnecessary performance loss, thus affecting the user experience.

solution

To improve performance, map instances can be cached so that the map can be re-rendered when needed rather than having to be recreated each time. Here are some ways to achieve this:

  1. use React context:
  • Advantages: React Context is a built-in feature of React and does not require additional libraries to be installed. It allows you to easily share state across your component tree without having to pass data layer by layer through props.
  • Disadvantages: Context is not suitable for storing a large amount of state, because every time the Context is updated, all components using the Context will be re-rendered. In addition, the use of Context may complicate the code, especially in large applications.
  1. Use localStorage or sessionStorage:
  • Advantages: localStorage and sessionStorage are part of Web API, no need to install any libraries. They allow you to store data persistently in the user's browser, retaining data even after page refreshes.
  • Disadvantages: localStorage and sessionStorage have limited storage space (usually 5MB). Additionally, they can only store strings, which means you need to convert the object to a string to store, and parse it back into an object when reading.

Since my project already uses Redux for state management, I chose to use Redux to implement caching and reuse of map instances.

Implementation steps

Use Provider to wrap the application: In the main entry file (such as main.js), use Provider to wrap the entire React application. This allows your app to access Redux storage.

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { Provider } from 'react-redux'
import { store } from '@/store/store'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
)

Create a Redux Slice to manage the state of the map instance. In the example, we created the mapSlice.ts file and defined the setMap reducer to save the map instance.

import { createSlice } from '@reduxjs/toolkit'

interface map {
  amap: any
}

// 默认值
const initialState: map = {
  amap: null  // 默认值为null
}

export const mapSlice = createSlice({
  name: 'mapInstance',
  initialState,
  reducers: {
    // 保存地图实例
    setMap: (state, action) => {
      state.amap = action.payload
    }
  }
})

export const { setMap } = mapSlice.actions

export default mapSlice.reducer

import { configureStore } from '@reduxjs/toolkit'
import mapSlice from './map/mapSlice'

export const store = configureStore({
  reducer: {
    map: mapSlice
  }
})

// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

import { useEffect } from 'react'
import AMapLoader from '@amap/amap-jsapi-loader'
import { useDispatch, useSelector } from 'react-redux'
import { setMap } from '@/store/map/mapSlice'

const MapContainer: React.FC = () => {
  // 获取之前保存的地图实例
  const { amap } = useSelector(state => state.map)
  // 用于调用action
  const dispatch = useDispatch()

  let map = amap // 地图实例

  useEffect(() => {
    // 存在地图实例时直接使用现有实例
    if (map !== null) {
      const containerParent = document.getElementById('containerParent')
      if (containerParent !== null) {
        // 将空容器替换成地图
        const container = document.getElementById('container')
        if (container !== null) {
          containerParent.removeChild(container)
        }
        containerParent.insertBefore(map.getContainer(), containerParent.firstChild)
      }
    } else {
      // 不存在地图实例时重新加载地图
      AMapLoader.load({
        key: 'xxxxxxx', // 高德地图Web端开发者Key
        version: '2.0',
        plugins: [] // 需要使用的的插件列表(必填项)
      })
        .then((AMap) => {
          map = new AMap.Map('container', {
            viewMode: '3D', // 3D地图模式
            zoom: 17, // 地图比例尺
            center: [120, 30] // 初始化地图中心点位置
          })
          
          ......
          ......
          
        })
        .catch((e) => {
          console.log(e)
        })
    }
    // 组件卸载时保存地图实例
    return () => {
      dispatch(setMap(map))
    }
  }, [])

  return (
    <div id='containerParent' style={{ position: 'relative', width: '100%', height: 'calc(100vh - 64px)' }}>
      <div id="container" style={{ width: '100%', height: '100%' }}></div>
    </div>
  )
}

export default MapContainer

Icing on the Cake

Every time you return to the map page, the map will be reloaded. Since the map information depends on the network, the slower the network speed, the longer the map loading delay vs. Every time you return to the map page, the map is already loaded.


加载评论