mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(vue): lifecycle events are correctly fired in component context (#22348)
resolves #22338
This commit is contained in:
@@ -15,5 +15,5 @@ export default {
|
||||
plugins: [terser()]
|
||||
}
|
||||
],
|
||||
external: ['vue-router']
|
||||
external: ['vue-router', 'vue']
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AnimationBuilder } from '@ionic/core';
|
||||
import { RouteLocationMatched, RouterOptions } from 'vue-router';
|
||||
import { Ref } from 'vue';
|
||||
|
||||
export interface IonicVueRouterOptions extends RouterOptions {
|
||||
tabsPrefix?: string;
|
||||
@@ -40,6 +41,7 @@ export interface ViewItem {
|
||||
mount: boolean;
|
||||
exact: boolean;
|
||||
registerCallback?: () => void;
|
||||
vueComponentRef: Ref;
|
||||
}
|
||||
|
||||
export interface ViewStacks {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RouteInfo,
|
||||
ViewStacks,
|
||||
} from './types';
|
||||
import { RouteLocationMatched } from 'vue-router';
|
||||
import { shallowRef } from 'vue';
|
||||
|
||||
export const createViewStacks = () => {
|
||||
let viewStacks: ViewStacks = {};
|
||||
@@ -95,6 +96,7 @@ export const createViewStacks = () => {
|
||||
matchedRoute,
|
||||
ionPageElement: ionPage,
|
||||
vueComponent,
|
||||
vueComponentRef: shallowRef(),
|
||||
ionRoute: false,
|
||||
mount: false,
|
||||
exact: routeInfo.pathname === matchedRoute.path
|
||||
|
||||
@@ -182,13 +182,13 @@ export const IonRouterOutlet = defineComponent({
|
||||
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, LIFECYCLE_WILL_ENTER);
|
||||
fireLifecycle(enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
|
||||
|
||||
if (leavingViewItem) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_WILL_LEAVE);
|
||||
fireLifecycle(leavingViewItem.vueComponentRef, LIFECYCLE_WILL_LEAVE);
|
||||
|
||||
/**
|
||||
* If we are going back from a page that
|
||||
@@ -230,7 +230,7 @@ export const IonRouterOutlet = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_DID_LEAVE);
|
||||
fireLifecycle(leavingViewItem.vueComponentRef, LIFECYCLE_DID_LEAVE);
|
||||
} else {
|
||||
/**
|
||||
* If there is no leaving element, just show
|
||||
@@ -241,7 +241,7 @@ export const IonRouterOutlet = defineComponent({
|
||||
requestAnimationFrame(() => enteringEl.classList.remove('ion-page-invisible'));
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, LIFECYCLE_DID_ENTER);
|
||||
fireLifecycle(enteringViewItem.vueComponentRef, LIFECYCLE_DID_ENTER);
|
||||
|
||||
components.value = viewStacks.getChildrenToRender(id);
|
||||
}
|
||||
@@ -359,6 +359,7 @@ export const IonRouterOutlet = defineComponent({
|
||||
return h(
|
||||
c.vueComponent,
|
||||
{
|
||||
ref: c.vueComponentRef,
|
||||
key: c.pathname,
|
||||
isInOutlet: true,
|
||||
registerIonPage: (ionPageEl: HTMLElement) => registerIonPage(c, ionPageEl)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Ref } from 'vue';
|
||||
|
||||
export const LIFECYCLE_WILL_ENTER = 'ionViewWillEnter';
|
||||
export const LIFECYCLE_DID_ENTER = 'ionViewDidEnter';
|
||||
export const LIFECYCLE_WILL_LEAVE = 'ionViewWillLeave';
|
||||
@@ -12,8 +14,8 @@ export const generateId = (type = 'main') => {
|
||||
};
|
||||
|
||||
// TODO types
|
||||
export const fireLifecycle = (vueComponent: any, lifecycle: string) => {
|
||||
if (vueComponent && vueComponent.methods && vueComponent.methods[lifecycle]) {
|
||||
vueComponent.methods[lifecycle]();
|
||||
export const fireLifecycle = (vueComponentRef: Ref<any>, lifecycle: string) => {
|
||||
if (vueComponentRef && vueComponentRef.value && vueComponentRef.value[lifecycle]) {
|
||||
vueComponentRef.value[lifecycle]();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: '/',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/lifecycle',
|
||||
component: () => import('@/views/Lifecycle.vue')
|
||||
},
|
||||
{
|
||||
path: '/overlays',
|
||||
name: 'Overlays',
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
<ion-item router-link="/tabs-secondary" id="tab-secondary">
|
||||
<ion-label>Tabs Secondary</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/lifecycle" id="lifecycle">
|
||||
<ion-label>Lifecycle</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
||||
77
packages/vue/test-app/src/views/Lifecycle.vue
Normal file
77
packages/vue/test-app/src/views/Lifecycle.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<ion-page data-pageid="lifecycle">
|
||||
<ion-header :translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-buttons>
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Lifecycle</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Lifecycle</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div class="ion-padding">
|
||||
ionViewWillEnter: <div id="willEnter">{{ willEnter }}</div><br />
|
||||
ionViewDidEnter: <div id="didEnter">{{ didEnter }}</div><br />
|
||||
ionViewWillLeave: <div id="willLeave">{{ willLeave }}</div><br />
|
||||
ionViewDidLeave: <div id="didLeave">{{ didLeave }}</div><br />
|
||||
|
||||
<ion-button router-link="/navigation" id="lifecycle-navigation">Go to another page</ion-button>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
IonButton,
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
} from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IonButton,
|
||||
IonBackButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
},
|
||||
methods: {
|
||||
ionViewWillEnter() {
|
||||
this.willEnter++;
|
||||
},
|
||||
ionViewDidEnter() {
|
||||
this.didEnter++;
|
||||
},
|
||||
ionViewWillLeave() {
|
||||
this.willLeave++;
|
||||
},
|
||||
ionViewDidLeave() {
|
||||
this.didLeave++;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
willEnter: 0,
|
||||
didEnter: 0,
|
||||
willLeave: 0,
|
||||
didLeave: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
49
packages/vue/test-app/tests/e2e/specs/lifecycle.js
Normal file
49
packages/vue/test-app/tests/e2e/specs/lifecycle.js
Normal file
@@ -0,0 +1,49 @@
|
||||
describe('Lifecycle', () => {
|
||||
it('should fire lifecycle events when navigating to and from a page', () => {
|
||||
cy.visit('http://localhost:8080');
|
||||
cy.get('#lifecycle').click();
|
||||
|
||||
testLifecycle('lifecycle', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0
|
||||
});
|
||||
|
||||
cy.get('#lifecycle-navigation').click();
|
||||
|
||||
testLifecycle('lifecycle', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1
|
||||
});
|
||||
|
||||
cy.ionBackClick('navigation');
|
||||
|
||||
testLifecycle('lifecycle', {
|
||||
ionViewWillEnter: 2,
|
||||
ionViewDidEnter: 2,
|
||||
ionViewWillLeave: 1,
|
||||
ionViewDidLeave: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('should fire lifecycle events when landed on directly', () => {
|
||||
cy.visit('http://localhost:8080/lifecycle');
|
||||
|
||||
testLifecycle('lifecycle', {
|
||||
ionViewWillEnter: 1,
|
||||
ionViewDidEnter: 1,
|
||||
ionViewWillLeave: 0,
|
||||
ionViewDidLeave: 0
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
const testLifecycle = (selector, expected = {}) => {
|
||||
cy.get(`[data-pageid=${selector}] #willEnter`).should('have.text', expected.ionViewWillEnter);
|
||||
cy.get(`[data-pageid=${selector}] #didEnter`).should('have.text', expected.ionViewDidEnter);
|
||||
cy.get(`[data-pageid=${selector}] #willLeave`).should('have.text', expected.ionViewWillLeave);
|
||||
cy.get(`[data-pageid=${selector}] #didLeave`).should('have.text', expected.ionViewDidLeave);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
|
||||
describe('HelloWorld.vue', () => {
|
||||
it('renders props.msg when passed', () => {
|
||||
const msg = 'new message'
|
||||
const wrapper = shallowMount(HelloWorld, {
|
||||
props: { msg }
|
||||
})
|
||||
expect(wrapper.text()).toMatch(msg)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user