Skip to content

Commit 1e160c6

Browse files
author
danhnguyen
committed
feat: Optimize prop watchers in LineLayer and SymbolLayer components for improved performance
feat: Enhance Mapbox component with sensible default options and optimized computed properties refactor: Simplify and streamline useLayerEventListener and useMapEventListener for better efficiency refactor: Update useCreateMapbox to simplify camera controls and remove unnecessary methods chore: Add useDebounce and useOptimizedComputed utilities for better performance and reactivity fix: Improve Vite configuration for better build optimization and dependency management docs: Update package.json for better type definitions and module exports
1 parent 65335a4 commit 1e160c6

16 files changed

+1041
-333
lines changed

libs/components/CircleLayer.vue

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { ref, inject, watch, watchEffect, computed } from 'vue';
2+
import { ref, inject, watch, computed, onUnmounted } from 'vue';
33
import {
44
MapProvideKey,
55
SourceProvideKey,
@@ -157,40 +157,46 @@ MapboxLayerEvents.forEach((evt) => {
157157
});
158158
});
159159
160-
// Reactive watchers for prop changes with error handling
161-
watch(
162-
() => props.filter,
163-
(newFilter) => {
164-
setFilter(newFilter);
165-
},
166-
{ deep: true },
167-
);
168-
169-
watch(
170-
() => props.style,
171-
(newStyle) => {
172-
setStyle(newStyle);
173-
},
174-
{ deep: true },
175-
);
176-
177-
watch(
178-
() => [props.maxzoom, props.minzoom],
179-
([maxzoom, minzoom]) => {
180-
setZoomRange(minzoom, maxzoom);
160+
// Optimized single watcher for all prop changes to reduce overhead
161+
const stopPropsWatcher = watch(
162+
() => ({
163+
filter: props.filter,
164+
style: props.style,
165+
maxzoom: props.maxzoom,
166+
minzoom: props.minzoom,
167+
beforeId: props.beforeId,
168+
visible: props.visible,
169+
}),
170+
(newProps, oldProps) => {
171+
// Only update if values actually changed to prevent unnecessary operations
172+
if (newProps.filter !== oldProps?.filter) {
173+
setFilter(newProps.filter);
174+
}
175+
if (newProps.style !== oldProps?.style) {
176+
setStyle(newProps.style);
177+
}
178+
if (
179+
newProps.maxzoom !== oldProps?.maxzoom ||
180+
newProps.minzoom !== oldProps?.minzoom
181+
) {
182+
setZoomRange(newProps.minzoom, newProps.maxzoom);
183+
}
184+
if (newProps.beforeId !== oldProps?.beforeId) {
185+
setBeforeId(newProps.beforeId);
186+
}
187+
if (newProps.visible !== oldProps?.visible) {
188+
setLayoutProperty('visibility', newProps.visible ? 'visible' : 'none');
189+
}
181190
},
182-
);
183-
184-
watch(
185-
() => props.beforeId,
186-
(newBeforeId) => {
187-
setBeforeId(newBeforeId);
191+
{
192+
deep: true,
193+
flush: 'post', // Run after DOM updates for better performance
188194
},
189195
);
190196
191-
// Optimized visibility watcher using computed property
192-
watchEffect(() => {
193-
setLayoutProperty('visibility', props.visible ? 'visible' : 'none');
197+
// Enhanced cleanup
198+
onUnmounted(() => {
199+
stopPropsWatcher();
194200
});
195201
</script>
196202
<template></template>

libs/components/FillLayer.vue

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { ref, inject, watch, watchEffect, computed } from 'vue';
2+
import { ref, inject, watch, computed, onUnmounted } from 'vue';
33
import {
44
MapProvideKey,
55
SourceProvideKey,
@@ -151,40 +151,46 @@ MapboxLayerEvents.forEach((evt) => {
151151
});
152152
});
153153
154-
// Reactive watchers for prop changes with error handling
155-
watch(
156-
() => props.filter,
157-
(newFilter) => {
158-
setFilter(newFilter);
159-
},
160-
{ deep: true },
161-
);
162-
163-
watch(
164-
() => props.style,
165-
(newStyle) => {
166-
setStyle(newStyle);
167-
},
168-
{ deep: true },
169-
);
170-
171-
watch(
172-
() => [props.maxzoom, props.minzoom],
173-
([maxzoom, minzoom]) => {
174-
setZoomRange(minzoom, maxzoom);
154+
// Optimized single watcher for all prop changes to reduce overhead
155+
const stopPropsWatcher = watch(
156+
() => ({
157+
filter: props.filter,
158+
style: props.style,
159+
maxzoom: props.maxzoom,
160+
minzoom: props.minzoom,
161+
beforeId: props.beforeId,
162+
visible: props.visible,
163+
}),
164+
(newProps, oldProps) => {
165+
// Only update if values actually changed to prevent unnecessary operations
166+
if (newProps.filter !== oldProps?.filter) {
167+
setFilter(newProps.filter);
168+
}
169+
if (newProps.style !== oldProps?.style) {
170+
setStyle(newProps.style);
171+
}
172+
if (
173+
newProps.maxzoom !== oldProps?.maxzoom ||
174+
newProps.minzoom !== oldProps?.minzoom
175+
) {
176+
setZoomRange(newProps.minzoom, newProps.maxzoom);
177+
}
178+
if (newProps.beforeId !== oldProps?.beforeId) {
179+
setBeforeId(newProps.beforeId);
180+
}
181+
if (newProps.visible !== oldProps?.visible) {
182+
setLayoutProperty('visibility', newProps.visible ? 'visible' : 'none');
183+
}
175184
},
176-
);
177-
178-
watch(
179-
() => props.beforeId,
180-
(newBeforeId) => {
181-
setBeforeId(newBeforeId);
185+
{
186+
deep: true,
187+
flush: 'post', // Run after DOM updates for better performance
182188
},
183189
);
184190
185-
// Optimized visibility watcher using computed property
186-
watchEffect(() => {
187-
setLayoutProperty('visibility', props.visible ? 'visible' : 'none');
191+
// Enhanced cleanup
192+
onUnmounted(() => {
193+
stopPropsWatcher();
188194
});
189195
</script>
190196
<template></template>

libs/components/GeoJsonSource.vue

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<script lang="ts" setup>
2-
import { inject, ref, provide, watch, computed, onUnmounted } from 'vue';
2+
import { inject, ref, provide, computed, onUnmounted } from 'vue';
33
import { MapProvideKey, SourceProvideKey } from '@libs/enums';
4-
import { useCreateGeoJsonSource, useLogger } from '@libs/composables';
4+
import {
5+
useCreateGeoJsonSource,
6+
useLogger,
7+
useDebouncedWatch,
8+
} from '@libs/composables';
59
import type { CreateGeoJsonSourceActions } from '@libs/composables';
610
import type { GeoJSONSource, GeoJSONSourceSpecification } from 'maplibre-gl';
711
@@ -27,6 +31,8 @@ interface GeoJsonSourceProps {
2731
onLoad?: (source: GeoJSONSource) => void;
2832
/** Data update callback */
2933
onDataUpdate?: (data: GeoJSONSourceSpecification['data']) => void;
34+
/** Debounce delay for data updates in milliseconds (default: 100) */
35+
debounceDelay?: number;
3036
}
3137
3238
/**
@@ -47,6 +53,7 @@ const props = withDefaults(defineProps<GeoJsonSourceProps>(), {
4753
options: () => ({}),
4854
debug: false,
4955
autoCleanup: true,
56+
debounceDelay: 100,
5057
});
5158
5259
const emits = defineEmits<Emits>();
@@ -157,8 +164,8 @@ const {
157164
// Provide source to child components
158165
provide(SourceProvideKey, getSource);
159166
160-
// Enhanced data watcher with validation and error handling
161-
watch(
167+
// Enhanced debounced data watcher with validation and error handling for better performance
168+
const stopDataWatcher = useDebouncedWatch(
162169
() => props.data,
163170
(newData, oldData) => {
164171
if (newData === oldData) return;
@@ -179,9 +186,11 @@ watch(
179186
}
180187
},
181188
{
189+
delay: props.debounceDelay,
182190
deep: true,
183191
immediate: true,
184192
flush: 'post', // Optimize by running after DOM updates
193+
debug: props.debug,
185194
},
186195
);
187196
@@ -191,6 +200,9 @@ watch(
191200
function cleanup(): void {
192201
try {
193202
if (props.autoCleanup) {
203+
// Stop debounced watcher
204+
stopDataWatcher();
205+
194206
// Reset state
195207
isSourceRegistered.value = false;
196208
lastDataUpdate.value = undefined;

libs/components/LineLayer.vue

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { ref, inject, watch, watchEffect, computed } from 'vue';
2+
import { ref, inject, watch, computed, onUnmounted } from 'vue';
33
import {
44
MapProvideKey,
55
SourceProvideKey,
@@ -152,40 +152,46 @@ MapboxLayerEvents.forEach((evt) => {
152152
});
153153
});
154154
155-
// Reactive watchers for prop changes with error handling
156-
watch(
157-
() => props.filter,
158-
(newFilter) => {
159-
setFilter(newFilter);
160-
},
161-
{ deep: true },
162-
);
163-
164-
watch(
165-
() => props.style,
166-
(newStyle) => {
167-
setStyle(newStyle);
168-
},
169-
{ deep: true },
170-
);
171-
172-
watch(
173-
() => [props.maxzoom, props.minzoom],
174-
([maxzoom, minzoom]) => {
175-
setZoomRange(minzoom, maxzoom);
155+
// Optimized single watcher for all prop changes to reduce overhead
156+
const stopPropsWatcher = watch(
157+
() => ({
158+
filter: props.filter,
159+
style: props.style,
160+
maxzoom: props.maxzoom,
161+
minzoom: props.minzoom,
162+
beforeId: props.beforeId,
163+
visible: props.visible,
164+
}),
165+
(newProps, oldProps) => {
166+
// Only update if values actually changed to prevent unnecessary operations
167+
if (newProps.filter !== oldProps?.filter) {
168+
setFilter(newProps.filter);
169+
}
170+
if (newProps.style !== oldProps?.style) {
171+
setStyle(newProps.style);
172+
}
173+
if (
174+
newProps.maxzoom !== oldProps?.maxzoom ||
175+
newProps.minzoom !== oldProps?.minzoom
176+
) {
177+
setZoomRange(newProps.minzoom, newProps.maxzoom);
178+
}
179+
if (newProps.beforeId !== oldProps?.beforeId) {
180+
setBeforeId(newProps.beforeId);
181+
}
182+
if (newProps.visible !== oldProps?.visible) {
183+
setLayoutProperty('visibility', newProps.visible ? 'visible' : 'none');
184+
}
176185
},
177-
);
178-
179-
watch(
180-
() => props.beforeId,
181-
(newBeforeId) => {
182-
setBeforeId(newBeforeId);
186+
{
187+
deep: true,
188+
flush: 'post', // Run after DOM updates for better performance
183189
},
184190
);
185191
186-
// Optimized visibility watcher using computed property
187-
watchEffect(() => {
188-
setLayoutProperty('visibility', props.visible ? 'visible' : 'none');
192+
// Enhanced cleanup
193+
onUnmounted(() => {
194+
stopPropsWatcher();
189195
});
190196
</script>
191197
<template></template>

libs/components/Mapbox.vue

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
useCreateMapbox,
1616
useMapEventListener,
1717
useLogger,
18+
useOptimizedComputed,
1819
} from '@libs/composables';
1920
import type { CreateMaplibreActions, MaplibreActions } from '@libs/types';
2021
import type {
@@ -114,10 +115,28 @@ interface Emits {
114115
}
115116
116117
const props = withDefaults(defineProps<MapboxProps>(), {
117-
options: () => ({}),
118+
options: () => ({
119+
// Provide sensible defaults for better performance
120+
style: 'https://demotiles.maplibre.org/style.json',
121+
center: [0, 0] as [number, number],
122+
zoom: 1,
123+
pitch: 0,
124+
bearing: 0,
125+
antialias: true,
126+
optimizeForTerrain: true,
127+
// Performance optimizations
128+
preserveDrawingBuffer: false,
129+
refreshExpiredTiles: true,
130+
maxTileCacheSize: null,
131+
localIdeographFontFamily: false,
132+
transformRequest: undefined,
133+
collectResourceTiming: false,
134+
fadeDuration: 300,
135+
crossSourceCollisions: true,
136+
}),
118137
debug: false,
119138
autoCleanup: true,
120-
containerId: 'maplibre',
139+
containerId: () => `maplibre-${Math.random().toString(36).substring(2, 11)}`,
121140
containerClass: '',
122141
});
123142
const emits = defineEmits<Emits>();
@@ -136,17 +155,23 @@ const mapCreationStatus = ref<MapCreationStatus>(
136155
);
137156
138157
// Enhanced computed properties for better reactivity and performance
139-
const mapOptions = computed(() => {
140-
const baseOptions = { ...props.options };
141-
const mergedOptions = { ...baseOptions, ...innerOptions.value };
142-
143-
// Ensure container is properly set
144-
if (!mergedOptions.container) {
145-
mergedOptions.container = props.containerId;
146-
}
158+
const mapOptions = useOptimizedComputed(
159+
() => {
160+
const baseOptions = { ...props.options };
161+
const mergedOptions = { ...baseOptions, ...innerOptions.value };
162+
163+
// Ensure container is properly set
164+
if (!mergedOptions.container) {
165+
mergedOptions.container = props.containerId;
166+
}
147167
148-
return mergedOptions;
149-
});
168+
return mergedOptions;
169+
},
170+
{
171+
deepEqual: true, // Use deep equality for complex objects
172+
debug: props.debug,
173+
},
174+
);
150175
151176
const isMapReady = computed(
152177
() => mapCreationStatus.value === MapCreationStatus.Loaded,

0 commit comments

Comments
 (0)