mirror of
https://github.com/visgl/react-map-gl.git
synced 2025-12-08 20:16:02 +00:00
Add reuseMaps prop (#1730)
This commit is contained in:
parent
fe779d77b0
commit
f666ef87d2
@ -634,6 +634,16 @@ Default: `true`
|
||||
|
||||
If `false`, the map won't attempt to re-request tiles once they expire per their HTTP `cacheControl`/`expires` headers.
|
||||
|
||||
#### `reuseMaps`: boolean
|
||||
|
||||
Default: `false`
|
||||
|
||||
By default, every time a map component is unmounted, all internal resources associated with the underlying `Map` instance are released. If the map gets mounted again, a new `Map` instance is constructed.
|
||||
|
||||
If `reuseMaps` is set to `true`, when a map component is unmounted, the underlying `Map` instance is retained in memory. The next time a map component gets mounted, the saved instance is reused. This behavior may be desirable if an application frequently mounts/unmounts map(s), for example in a tabbed or collapsable UI, and wants to avoid [new billable events](https://github.com/mapbox/mapbox-gl-js/releases/tag/v2.0.0) triggered by initialization.
|
||||
|
||||
Note that since some map options cannot be modified after initialization, when reusing maps, only the reactive props and `initialViewState` of the new component are respected.
|
||||
|
||||
#### `RTLTextPlugin`: string
|
||||
|
||||
Default: `'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js'`
|
||||
|
||||
@ -28,6 +28,7 @@ export const MapContext = React.createContext<MapContextValue>(null);
|
||||
export type MapProps = MapboxProps &
|
||||
GlobalSettings & {
|
||||
mapLib?: any;
|
||||
reuseMaps?: boolean;
|
||||
/** Map container id */
|
||||
id?: string;
|
||||
/** Map container CSS style */
|
||||
@ -94,8 +95,12 @@ const Map = forwardRef<MapRef, MapProps>((props, ref) => {
|
||||
|
||||
if (mapboxgl.supported(props)) {
|
||||
setGlobals(mapboxgl, props);
|
||||
mapbox = new Mapbox(mapboxgl.Map, props);
|
||||
mapbox.initialize(containerRef.current);
|
||||
if (props.reuseMaps) {
|
||||
mapbox = Mapbox.reuse(props, containerRef.current);
|
||||
}
|
||||
if (!mapbox) {
|
||||
mapbox = new Mapbox(mapboxgl.Map, props, containerRef.current);
|
||||
}
|
||||
contextValue.map = mapbox.map;
|
||||
contextValue.mapLib = mapboxgl;
|
||||
|
||||
@ -118,7 +123,11 @@ const Map = forwardRef<MapRef, MapProps>((props, ref) => {
|
||||
isMounted = false;
|
||||
if (mapbox) {
|
||||
mountedMapsContext?.onMapUnmount(props.id);
|
||||
mapbox.destroy();
|
||||
if (props.reuseMaps) {
|
||||
mapbox.recycle();
|
||||
} else {
|
||||
mapbox.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -429,9 +429,12 @@ export default class Mapbox {
|
||||
rotate: false
|
||||
};
|
||||
|
||||
constructor(MapClass: typeof MapboxMap, props: MapboxProps) {
|
||||
static savedMaps: Mapbox[] = [];
|
||||
|
||||
constructor(MapClass: typeof MapboxMap, props: MapboxProps, container: HTMLDivElement) {
|
||||
this._MapClass = MapClass;
|
||||
this.props = props;
|
||||
this._initialize(container);
|
||||
}
|
||||
|
||||
get map(): MapboxMap {
|
||||
@ -464,7 +467,42 @@ export default class Mapbox {
|
||||
}
|
||||
}
|
||||
|
||||
initialize(container: HTMLDivElement) {
|
||||
static reuse(props: MapboxProps, container: HTMLDivElement) {
|
||||
const that = Mapbox.savedMaps.pop();
|
||||
if (!that) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const map = that.map;
|
||||
// When reusing the saved map, we need to reparent the map(canvas) and other child nodes
|
||||
// intoto the new container from the props.
|
||||
// Step1: reparenting child nodes from old container to new container
|
||||
const oldContainer = map.getContainer();
|
||||
container.className = oldContainer.className;
|
||||
while (oldContainer.childNodes.length > 0) {
|
||||
container.appendChild(oldContainer.childNodes[0]);
|
||||
}
|
||||
// Step2: replace the internal container with new container from the react component
|
||||
// @ts-ignore
|
||||
map._container = container;
|
||||
|
||||
// Step 3: apply new props
|
||||
if (props.initialViewState) {
|
||||
that._updateViewState(props.initialViewState, false);
|
||||
}
|
||||
map.resize();
|
||||
that.setProps({...props, styleDiffing: false});
|
||||
|
||||
// Simulate load event
|
||||
if (map.isStyleLoaded()) {
|
||||
map.fire('load');
|
||||
} else {
|
||||
map.once('styledata', () => map.fire('load'));
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
_initialize(container: HTMLDivElement) {
|
||||
const {props} = this;
|
||||
const mapOptions = {
|
||||
...props,
|
||||
@ -538,6 +576,10 @@ export default class Mapbox {
|
||||
this._map = map;
|
||||
}
|
||||
|
||||
recycle() {
|
||||
Mapbox.savedMaps.push(this);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._map.remove();
|
||||
}
|
||||
|
||||
32
test/apps/reuse-maps/index.html
Normal file
32
test/apps/reuse-maps/index.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='UTF-8' />
|
||||
<title>react-map-gl Example</title>
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
button {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src='app.js'></script>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
App.renderToDom(document.getElementById('app'));
|
||||
</script>
|
||||
</html>
|
||||
23
test/apps/reuse-maps/package.json
Normal file
23
test/apps/reuse-maps/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --progress --hot --open",
|
||||
"start-local": "webpack-dev-server --env local --progress --hot --open"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-map-gl": "^7.0.0",
|
||||
"mapbox-gl": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"ts-loader": "^9.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-cli": "^4.9.0",
|
||||
"webpack-dev-server": "^4.7.0"
|
||||
}
|
||||
}
|
||||
59
test/apps/reuse-maps/src/app.tsx
Normal file
59
test/apps/reuse-maps/src/app.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
import {render} from 'react-dom';
|
||||
import Map from 'react-map-gl';
|
||||
|
||||
const TOKEN = ''; // Set your mapbox token here
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
style: {width: '100%', height: '100%'},
|
||||
mapStyle: 'mapbox://styles/mapbox/dark-v9',
|
||||
initialViewState: {
|
||||
longitude: -122.4,
|
||||
latitude: 37.8,
|
||||
zoom: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
style: {width: 400, height: 300, margin: 100},
|
||||
mapStyle: 'mapbox://styles/mapbox/light-v9',
|
||||
initialViewState: {
|
||||
longitude: -100,
|
||||
latitude: 40,
|
||||
zoom: 3.5
|
||||
}
|
||||
},
|
||||
{
|
||||
style: {width: '50vw', height: '100vh', marginLeft: '50vw'},
|
||||
mapStyle: 'mapbox://styles/mapbox/streets-v9',
|
||||
longitude: -70.4,
|
||||
latitude: 40.1,
|
||||
zoom: 6
|
||||
}
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
const [key, setKey] = useState(0);
|
||||
const [showMap, setShowMap] = useState(true);
|
||||
|
||||
const onClickBtn = () => {
|
||||
if (!showMap) {
|
||||
setKey((key + 1) % CONFIGS.length);
|
||||
}
|
||||
setShowMap(!showMap);
|
||||
};
|
||||
|
||||
const onLoad = () => console.log(key, 'loaded'); // eslint-disable-line
|
||||
|
||||
return (
|
||||
<>
|
||||
{showMap && <Map {...CONFIGS[key]} reuseMaps mapboxAccessToken={TOKEN} onLoad={onLoad} />}
|
||||
<button onClick={onClickBtn}>switch</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
render(<App />, container);
|
||||
}
|
||||
10
test/apps/reuse-maps/tsconfig.json
Normal file
10
test/apps/reuse-maps/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"jsx": "react",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"module": "ES2020"
|
||||
}
|
||||
}
|
||||
54
test/apps/reuse-maps/webpack.config.js
Normal file
54
test/apps/reuse-maps/webpack.config.js
Normal file
@ -0,0 +1,54 @@
|
||||
// NOTE: To use this example standalone (e.g. outside of repo)
|
||||
// delete the local development overrides at the bottom of this file
|
||||
|
||||
// avoid destructuring for older Node version support
|
||||
const resolve = require('path').resolve;
|
||||
const webpack = require('webpack');
|
||||
|
||||
const config = {
|
||||
mode: 'development',
|
||||
|
||||
devServer: {
|
||||
static: '.'
|
||||
},
|
||||
|
||||
entry: {
|
||||
app: resolve('./src/app')
|
||||
},
|
||||
|
||||
output: {
|
||||
library: 'App'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.json']
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|js)x?$/,
|
||||
include: [resolve('.')],
|
||||
exclude: [/node_modules/],
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/env', '@babel/react']
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Optional: Enables reading mapbox token from environment variable
|
||||
plugins: [new webpack.EnvironmentPlugin({MapboxAccessToken: ''})]
|
||||
};
|
||||
|
||||
// Enables bundling against src in this repo rather than the installed version
|
||||
module.exports = env =>
|
||||
env && env.local ? require('../../../examples/webpack.config.local')(config)(env) : config;
|
||||
Loading…
x
Reference in New Issue
Block a user