mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00

Issue number: Resolves #28186 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Ionic lifecycle hooks do not execute a cleanup function when the underlying `useEffect` is unmounted. ```ts useEffect(() => { return () => { console.log('cleanup'); // called }; }); useIonViewWillEnter(() => { return () => { console.log('cleanup'); // never called }; }); ``` Ionic's implementation registers the lifecycle callback to be handled at a later time, by the page managers. However, it does not keep a reference to the returned callback, so it cannot execute it when the `useEffect` is unmounted. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Ionic lifecycle hooks execute dev-specified cleanup functions ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev-build: `7.4.4-dev.11696956070.1faa3cfe` This PR builds on the changes in #28316. --------- Co-authored-by: Maria Hutt <maria@ionic.io> Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com>
228 lines
6.7 KiB
TypeScript
228 lines
6.7 KiB
TypeScript
import React from 'react';
|
|
|
|
export interface IonLifeCycleContextInterface {
|
|
onIonViewWillEnter: (callback: () => void) => void;
|
|
ionViewWillEnter: () => void;
|
|
onIonViewDidEnter: (callback: () => void) => void;
|
|
ionViewDidEnter: () => void;
|
|
onIonViewWillLeave: (callback: () => void) => void;
|
|
ionViewWillLeave: () => void;
|
|
onIonViewDidLeave: (callback: () => void) => void;
|
|
ionViewDidLeave: () => void;
|
|
cleanupIonViewWillEnter: (callback: () => void) => void;
|
|
cleanupIonViewDidEnter: (callback: () => void) => void;
|
|
cleanupIonViewWillLeave: (callback: () => void) => void;
|
|
cleanupIonViewDidLeave: (callback: () => void) => void;
|
|
}
|
|
|
|
export const IonLifeCycleContext = /*@__PURE__*/ React.createContext<IonLifeCycleContextInterface>({
|
|
onIonViewWillEnter: () => {
|
|
return;
|
|
},
|
|
ionViewWillEnter: () => {
|
|
return;
|
|
},
|
|
onIonViewDidEnter: () => {
|
|
return;
|
|
},
|
|
ionViewDidEnter: () => {
|
|
return;
|
|
},
|
|
onIonViewWillLeave: () => {
|
|
return;
|
|
},
|
|
ionViewWillLeave: () => {
|
|
return;
|
|
},
|
|
onIonViewDidLeave: () => {
|
|
return;
|
|
},
|
|
ionViewDidLeave: () => {
|
|
return;
|
|
},
|
|
cleanupIonViewWillEnter: () => {
|
|
return;
|
|
},
|
|
cleanupIonViewDidEnter: () => {
|
|
return;
|
|
},
|
|
cleanupIonViewWillLeave: () => {
|
|
return;
|
|
},
|
|
cleanupIonViewDidLeave: () => {
|
|
return;
|
|
},
|
|
});
|
|
|
|
export interface LifeCycleCallback {
|
|
(): void | (() => void | undefined);
|
|
id?: number;
|
|
}
|
|
|
|
export interface LifeCycleDestructor {
|
|
id: number;
|
|
destructor: ReturnType<LifeCycleCallback>;
|
|
}
|
|
|
|
export const DefaultIonLifeCycleContext = class implements IonLifeCycleContextInterface {
|
|
ionViewWillEnterCallbacks: LifeCycleCallback[] = [];
|
|
ionViewDidEnterCallbacks: LifeCycleCallback[] = [];
|
|
ionViewWillLeaveCallbacks: LifeCycleCallback[] = [];
|
|
ionViewDidLeaveCallbacks: LifeCycleCallback[] = [];
|
|
componentCanBeDestroyedCallback?: () => void;
|
|
ionViewWillEnterDestructorCallbacks: LifeCycleDestructor[] = [];
|
|
ionViewDidEnterDestructorCallbacks: LifeCycleDestructor[] = [];
|
|
ionViewWillLeaveDestructorCallbacks: LifeCycleDestructor[] = [];
|
|
ionViewDidLeaveDestructorCallbacks: LifeCycleDestructor[] = [];
|
|
|
|
onIonViewWillEnter(callback: LifeCycleCallback) {
|
|
if (callback.id) {
|
|
const index = this.ionViewWillEnterCallbacks.findIndex((x) => x.id === callback.id);
|
|
if (index > -1) {
|
|
this.ionViewWillEnterCallbacks[index] = callback;
|
|
} else {
|
|
this.ionViewWillEnterCallbacks.push(callback);
|
|
}
|
|
} else {
|
|
this.ionViewWillEnterCallbacks.push(callback);
|
|
}
|
|
}
|
|
|
|
teardownCallback(callback: LifeCycleCallback, callbacks: any[]) {
|
|
// Find any destructors that have been registered for the callback
|
|
const matches = callbacks.filter((x) => x.id === callback.id);
|
|
if (matches.length !== 0) {
|
|
// Execute the destructor for each matching item
|
|
matches.forEach((match) => {
|
|
if (match && typeof match.destructor === 'function') {
|
|
match.destructor();
|
|
}
|
|
});
|
|
// Remove all matching items from the array
|
|
callbacks = callbacks.filter((x) => x.id !== callback.id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tears down the user-provided ionViewWillEnter lifecycle callback.
|
|
* This is the same behavior as React's useEffect hook. The callback
|
|
* is invoked when the component is unmounted.
|
|
*/
|
|
cleanupIonViewWillEnter(callback: LifeCycleCallback) {
|
|
this.teardownCallback(callback, this.ionViewWillEnterDestructorCallbacks);
|
|
}
|
|
|
|
/**
|
|
* Tears down the user-provided ionViewDidEnter lifecycle callback.
|
|
* This is the same behavior as React's useEffect hook. The callback
|
|
* is invoked when the component is unmounted.
|
|
*/
|
|
cleanupIonViewDidEnter(callback: LifeCycleCallback) {
|
|
this.teardownCallback(callback, this.ionViewDidEnterDestructorCallbacks);
|
|
}
|
|
|
|
/**
|
|
* Tears down the user-provided ionViewWillLeave lifecycle callback.
|
|
* This is the same behavior as React's useEffect hook. The callback
|
|
* is invoked when the component is unmounted.
|
|
*/
|
|
cleanupIonViewWillLeave(callback: LifeCycleCallback) {
|
|
this.teardownCallback(callback, this.ionViewWillLeaveDestructorCallbacks);
|
|
}
|
|
|
|
/**
|
|
* Tears down the user-provided ionViewDidLeave lifecycle callback.
|
|
* This is the same behavior as React's useEffect hook. The callback
|
|
* is invoked when the component is unmounted.
|
|
*/
|
|
cleanupIonViewDidLeave(callback: LifeCycleCallback) {
|
|
this.teardownCallback(callback, this.ionViewDidLeaveDestructorCallbacks);
|
|
}
|
|
|
|
ionViewWillEnter() {
|
|
this.ionViewWillEnterCallbacks.forEach((cb) => {
|
|
const destructor = cb();
|
|
if (cb.id) {
|
|
this.ionViewWillEnterDestructorCallbacks.push({ id: cb.id, destructor });
|
|
}
|
|
});
|
|
}
|
|
|
|
onIonViewDidEnter(callback: LifeCycleCallback) {
|
|
if (callback.id) {
|
|
const index = this.ionViewDidEnterCallbacks.findIndex((x) => x.id === callback.id);
|
|
if (index > -1) {
|
|
this.ionViewDidEnterCallbacks[index] = callback;
|
|
} else {
|
|
this.ionViewDidEnterCallbacks.push(callback);
|
|
}
|
|
} else {
|
|
this.ionViewDidEnterCallbacks.push(callback);
|
|
}
|
|
}
|
|
|
|
ionViewDidEnter() {
|
|
this.ionViewDidEnterCallbacks.forEach((cb) => {
|
|
const destructor = cb();
|
|
if (cb.id) {
|
|
this.ionViewDidEnterDestructorCallbacks.push({ id: cb.id, destructor });
|
|
}
|
|
});
|
|
}
|
|
|
|
onIonViewWillLeave(callback: LifeCycleCallback) {
|
|
if (callback.id) {
|
|
const index = this.ionViewWillLeaveCallbacks.findIndex((x) => x.id === callback.id);
|
|
if (index > -1) {
|
|
this.ionViewWillLeaveCallbacks[index] = callback;
|
|
} else {
|
|
this.ionViewWillLeaveCallbacks.push(callback);
|
|
}
|
|
} else {
|
|
this.ionViewWillLeaveCallbacks.push(callback);
|
|
}
|
|
}
|
|
|
|
ionViewWillLeave() {
|
|
this.ionViewWillLeaveCallbacks.forEach((cb) => {
|
|
const destructor = cb();
|
|
if (cb.id) {
|
|
this.ionViewWillLeaveDestructorCallbacks.push({ id: cb.id, destructor });
|
|
}
|
|
});
|
|
}
|
|
|
|
onIonViewDidLeave(callback: LifeCycleCallback) {
|
|
if (callback.id) {
|
|
const index = this.ionViewDidLeaveCallbacks.findIndex((x) => x.id === callback.id);
|
|
if (index > -1) {
|
|
this.ionViewDidLeaveCallbacks[index] = callback;
|
|
} else {
|
|
this.ionViewDidLeaveCallbacks.push(callback);
|
|
}
|
|
} else {
|
|
this.ionViewDidLeaveCallbacks.push(callback);
|
|
}
|
|
}
|
|
|
|
ionViewDidLeave() {
|
|
this.ionViewDidLeaveCallbacks.forEach((cb) => {
|
|
const destructor = cb();
|
|
if (cb.id) {
|
|
this.ionViewDidLeaveDestructorCallbacks.push({ id: cb.id, destructor });
|
|
}
|
|
});
|
|
this.componentCanBeDestroyed();
|
|
}
|
|
|
|
onComponentCanBeDestroyed(callback: () => void) {
|
|
this.componentCanBeDestroyedCallback = callback;
|
|
}
|
|
|
|
componentCanBeDestroyed() {
|
|
if (this.componentCanBeDestroyedCallback) {
|
|
this.componentCanBeDestroyedCallback();
|
|
}
|
|
}
|
|
};
|