Fix most of memory leaks

- Remove callback after component is unmounted
pull/6889/head
YannisJustine 2024-11-28 21:50:26 +01:00
parent 786f9c65e6
commit e165c82513
2 changed files with 72 additions and 21 deletions

View File

@ -45,10 +45,15 @@ export default {
}, },
dt: { dt: {
immediate: true, immediate: true,
handler(newValue) { handler(newValue, oldValue) {
if (oldValue) {
ThemeService.off('theme:change', this._themeScopedListener);
}
if (newValue) { if (newValue) {
this._loadScopedThemeStyles(newValue); this._loadScopedThemeStyles(newValue);
this._themeChangeListener(() => this._loadScopedThemeStyles(newValue)); this._themeScopedListener = () => this._loadScopedThemeStyles(newValue);
this._themeChangeListener(this._themeScopedListener)
} else { } else {
this._unloadScopedThemeStyles(); this._unloadScopedThemeStyles();
} }
@ -100,7 +105,8 @@ export default {
this._hook('onBeforeUnmount'); this._hook('onBeforeUnmount');
}, },
unmounted() { unmounted() {
this._unloadScopedThemeStyles(); ThemeService.off('theme:change', this._loadCoreStyles);
ThemeService.off('theme:change', this._load);
this._hook('onUnmounted'); this._hook('onUnmounted');
}, },
methods: { methods: {
@ -116,8 +122,7 @@ export default {
_mergeProps(fn, ...args) { _mergeProps(fn, ...args) {
return isFunction(fn) ? fn(...args) : mergeProps(...args); return isFunction(fn) ? fn(...args) : mergeProps(...args);
}, },
_loadStyles() { _load() {
const _load = () => {
// @todo // @todo
if (!Base.isStyleNameLoaded('base')) { if (!Base.isStyleNameLoaded('base')) {
BaseStyle.loadCSS(this.$styleOptions); BaseStyle.loadCSS(this.$styleOptions);
@ -127,10 +132,10 @@ export default {
} }
this._loadThemeStyles(); this._loadThemeStyles();
}; },
_loadStyles() {
_load(); this._load();
this._themeChangeListener(_load); this._themeChangeListener(this._load);
}, },
_loadCoreStyles() { _loadCoreStyles() {
if (!Base.isStyleNameLoaded(this.$style?.name) && this.$style?.name) { if (!Base.isStyleNameLoaded(this.$style?.name) && this.$style?.name) {

View File

@ -6,6 +6,9 @@ import BaseStyle from '@primevue/core/base/style';
import PrimeVueService from '@primevue/core/service'; import PrimeVueService from '@primevue/core/service';
import { mergeProps } from 'vue'; import { mergeProps } from 'vue';
const _themesCallback = new Map();
const _configCallback = new Map();
const BaseDirective = { const BaseDirective = {
_getMeta: (...args) => [isObject(args[0]) ? undefined : args[0], resolve(isObject(args[0]) ? args[0] : args[1])], _getMeta: (...args) => [isObject(args[0]) ? undefined : args[0], resolve(isObject(args[0]) ? args[0] : args[1])],
_getConfig: (binding, vnode) => (binding?.instance?.$primevue || vnode?.ctx?.appContext?.config?.globalProperties?.$primevue)?.config, _getConfig: (binding, vnode) => (binding?.instance?.$primevue || vnode?.ctx?.appContext?.config?.globalProperties?.$primevue)?.config,
@ -76,7 +79,9 @@ const BaseDirective = {
BaseDirective._loadThemeStyles(el.$instance, useStyleOptions); BaseDirective._loadThemeStyles(el.$instance, useStyleOptions);
BaseDirective._loadScopedThemeStyles(el.$instance, useStyleOptions); BaseDirective._loadScopedThemeStyles(el.$instance, useStyleOptions);
BaseDirective._themeChangeListener(() => BaseDirective._loadThemeStyles(el.$instance, useStyleOptions)); const loadStyle = () => BaseDirective._loadThemeStyles(el.$instance, useStyleOptions)
BaseDirective._themeChangeListener(el.$instance, loadStyle);
}, },
_loadCoreStyles(instance = {}, useStyleOptions) { _loadCoreStyles(instance = {}, useStyleOptions) {
if (!Base.isStyleNameLoaded(instance.$style?.name) && instance.$style?.name) { if (!Base.isStyleNameLoaded(instance.$style?.name) && instance.$style?.name) {
@ -130,10 +135,33 @@ const BaseDirective = {
instance.scopedStyleEl = scopedStyle.el; instance.scopedStyleEl = scopedStyle.el;
} }
}, },
_themeChangeListener(callback = () => {}) { _themeChangeListener(instance, callback = () => {}) {
if (!instance || !callback) return;
// Store callback reference
let listeners = _themesCallback.get(instance.$attrSelector);
if (!listeners) {
listeners = new Set();
_themesCallback.set(instance.$attrSelector, listeners);
}
listeners.add(callback);
Base.clearLoadedStyleNames(); Base.clearLoadedStyleNames();
ThemeService.on('theme:change', callback); ThemeService.on('theme:change', callback);
}, },
_removeThemeListeners(instance) {
const listeners = _themesCallback.get(instance.$attrSelector);
if (listeners) {
listeners.forEach(callback => {
ThemeService.off('theme:change', callback);
});
listeners.clear();
_themesCallback.delete(instance);
}
},
_hook: (directiveName, hookName, el, binding, vnode, prevVnode) => { _hook: (directiveName, hookName, el, binding, vnode, prevVnode) => {
const name = `on${toCapitalCase(hookName)}`; const name = `on${toCapitalCase(hookName)}`;
const config = BaseDirective._getConfig(binding, vnode); const config = BaseDirective._getConfig(binding, vnode);
@ -193,15 +221,31 @@ const BaseDirective = {
const handleWatch = (el) => { const handleWatch = (el) => {
const watchers = el.$instance?.watch; const watchers = el.$instance?.watch;
const handleWatchConfig = ({ newValue, oldValue }) => watchers?.['config']?.call(el.$instance, newValue, oldValue);
const handleWatchConfigRipple = ({ newValue, oldValue }) => watchers?.['config.ripple']?.call(el.$instance, newValue, oldValue)
_configCallback.set(el, { config: handleWatchConfig, 'config.ripple': handleWatchConfigRipple });
// for 'config' // for 'config'
watchers?.['config']?.call(el.$instance, el.$instance?.$primevueConfig); watchers?.['config']?.call(el.$instance, el.$instance?.$primevueConfig);
PrimeVueService.on('config:change', ({ newValue, oldValue }) => watchers?.['config']?.call(el.$instance, newValue, oldValue)); PrimeVueService.on('config:change', handleWatchConfig);
// for 'config.ripple' // for 'config.ripple'
watchers?.['config.ripple']?.call(el.$instance, el.$instance?.$primevueConfig?.ripple); watchers?.['config.ripple']?.call(el.$instance, el.$instance?.$primevueConfig?.ripple);
PrimeVueService.on('config:ripple:change', ({ newValue, oldValue }) => watchers?.['config.ripple']?.call(el.$instance, newValue, oldValue)); PrimeVueService.on('config:ripple:change', handleWatchConfigRipple);
}; };
const removeWatch = (el) => {
const watchers = _configCallback.get(el);
if (watchers) {
PrimeVueService.off('config:change', watchers.config);
PrimeVueService.off('config:ripple:change', watchers['config.ripple']);
_configCallback.delete(el);
}
}
return { return {
created: (el, binding, vnode, prevVnode) => { created: (el, binding, vnode, prevVnode) => {
el.$pd ||= {}; el.$pd ||= {};
@ -225,6 +269,8 @@ const BaseDirective = {
handleHook('updated', el, binding, vnode, prevVnode); handleHook('updated', el, binding, vnode, prevVnode);
}, },
beforeUnmount: (el, binding, vnode, prevVnode) => { beforeUnmount: (el, binding, vnode, prevVnode) => {
removeWatch(el);
BaseDirective._removeThemeListeners(el.$instance);
handleHook('beforeUnmount', el, binding, vnode, prevVnode); handleHook('beforeUnmount', el, binding, vnode, prevVnode);
}, },
unmounted: (el, binding, vnode, prevVnode) => { unmounted: (el, binding, vnode, prevVnode) => {