简介
问题
在项目开发中,为了避免内存泄漏,通常需要在组件卸载时销毁地图实例。然而,如果用户频繁地进入和离开地图页面,每次重新加载地图都会导致不必要的性能损耗,从而影响用户体验。
解决方案
为了提高性能,可以将地图实例进行缓存,以便在需要时重新渲染地图,而不必每次都重新创建。以下是一些实现此目标的方法:
- 使用React Context :
- 优点:React Context是React的内置功能,无需额外安装库。它可以让你在组件树中轻松共享状态,而无需通过props逐层传递数据。
- 缺点:Context不适合存储大量的状态,因为每次Context更新时,所有使用该Context的组件都会重新渲染。此外,Context的使用可能会使代码变得复杂,特别是在大型应用中。
- 使用Redux :
- 优点:Redux提供了一个集中的、可预测的状态管理解决方案。它允许你在应用的任何地方访问状态,同时还提供了强大的开发工具,如时间旅行调试。
- 缺点:Redux的学习曲线较陡峭,需要理解一些新概念(如reducers和actions)。此外,Redux可能会引入一些模板代码,特别是对于简单的应用来说。
- 使用localStorage或sessionStorage :
- 优点:localStorage和sessionStorage是Web API的一部分,无需安装任何库。它们允许你在用户的浏览器中持久化存储数据,即使在页面刷新后也能保留数据。
- 缺点:localStorage和sessionStorage的存储空间有限(通常为5MB)。此外,它们只能存储字符串,这意味着你需要将对象转换为字符串才能存储,并在读取时将其解析回对象。
由于我的项目已经使用Redux进行状态管理,因此选择使用Redux来实现地图实例的缓存和重用。
实现步骤
如果您已熟悉Redux,可以直接查看下面的实现步骤。否则,建议先访问Redux官网深入学习Redux,然后返回此处,开始从第3步了解如何在您的项目中使用Redux。
- 安装redux和redux toolkit
js
// NPM
npm install redux
npm install @reduxjs/toolkit
// Yarn
yarn add redux
yarn add @reduxjs/toolkit
- 使用
Provider
包装应用 : 在主入口文件(例如main.js
)中,使用Provider
来包装整个React应用。这允许您的应用访问Redux存储。
js
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>
)
- 创建一个Redux Slice来管理地图实例的状态。在示例中,我们创建了
mapSlice.ts
文件,并定义了setMap
reducer来保存地图实例。
js
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
- 创建Redux存储(store)来集中管理状态。示例中的
store.ts
文件定义了Redux存储并将mapSlice
添加到存储配置中。
js
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
- 在组件中使用Redux管理地图实例 : 在组件中使用
useSelector
和useDispatch
来访问Redux状态和操作。在示例中的MapContainer.tsx
文件中,我们获取保存的地图实例并在需要时进行重用。
js
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
效果对比
优化前:
每次回到地图页面都会重新加载地图,由于地图信息依赖网络。因此网速越慢地图加载延迟越久
优化后:
每次回到地图页面地图都是已经加载好的状态