mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 11:17:19 +08:00
fix(vue): pass in correct route to props function (#22605)
resolves #22602
This commit is contained in:
@ -2,6 +2,14 @@ import { AnimationBuilder } from '@ionic/core';
|
|||||||
import { RouteLocationMatched, RouterOptions } from 'vue-router';
|
import { RouteLocationMatched, RouterOptions } from 'vue-router';
|
||||||
import { Ref } from 'vue';
|
import { Ref } from 'vue';
|
||||||
|
|
||||||
|
export interface VueComponentData {
|
||||||
|
/**
|
||||||
|
* The cached result of the props
|
||||||
|
* function for a particular view instance.
|
||||||
|
*/
|
||||||
|
propsFunctionResult?: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IonicVueRouterOptions extends RouterOptions {
|
export interface IonicVueRouterOptions extends RouterOptions {
|
||||||
tabsPrefix?: string;
|
tabsPrefix?: string;
|
||||||
}
|
}
|
||||||
@ -43,6 +51,7 @@ export interface ViewItem {
|
|||||||
registerCallback?: () => void;
|
registerCallback?: () => void;
|
||||||
vueComponentRef: Ref;
|
vueComponentRef: Ref;
|
||||||
params?: { [k: string]: any };
|
params?: { [k: string]: any };
|
||||||
|
vueComponentData: VueComponentData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewStacks {
|
export interface ViewStacks {
|
||||||
|
@ -89,7 +89,8 @@ export const createViewStacks = () => {
|
|||||||
ionRoute: false,
|
ionRoute: false,
|
||||||
mount: false,
|
mount: false,
|
||||||
exact: routeInfo.pathname === matchedRoute.path,
|
exact: routeInfo.pathname === matchedRoute.path,
|
||||||
params: routeInfo.params
|
params: routeInfo.params,
|
||||||
|
vueComponentData: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,14 @@ import {
|
|||||||
onUnmounted
|
onUnmounted
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { AnimationBuilder, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core';
|
import { AnimationBuilder, LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core';
|
||||||
import { matchedRouteKey, useRoute } from 'vue-router';
|
import { matchedRouteKey, routeLocationKey, useRoute } from 'vue-router';
|
||||||
import { fireLifecycle, generateId, getConfig } from '../utils';
|
import { fireLifecycle, generateId, getConfig } from '../utils';
|
||||||
|
|
||||||
let viewDepthKey: InjectionKey<0> = Symbol(0);
|
let viewDepthKey: InjectionKey<0> = Symbol(0);
|
||||||
export const IonRouterOutlet = defineComponent({
|
export const IonRouterOutlet = defineComponent({
|
||||||
name: 'IonRouterOutlet',
|
name: 'IonRouterOutlet',
|
||||||
setup(_, { attrs }) {
|
setup(_, { attrs }) {
|
||||||
|
const injectedRoute = inject(routeLocationKey)!;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const depth = inject(viewDepthKey, 0);
|
const depth = inject(viewDepthKey, 0);
|
||||||
const matchedRouteRef: any = computed(() => {
|
const matchedRouteRef: any = computed(() => {
|
||||||
@ -352,12 +353,13 @@ export const IonRouterOutlet = defineComponent({
|
|||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
components,
|
components,
|
||||||
|
injectedRoute,
|
||||||
ionRouterOutlet,
|
ionRouterOutlet,
|
||||||
registerIonPage
|
registerIonPage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
const { components, registerIonPage } = this;
|
const { components, registerIonPage, injectedRoute } = this;
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
'ion-router-outlet',
|
'ion-router-outlet',
|
||||||
@ -374,14 +376,35 @@ export const IonRouterOutlet = defineComponent({
|
|||||||
/**
|
/**
|
||||||
* IonRouterOutlet does not support named outlets.
|
* IonRouterOutlet does not support named outlets.
|
||||||
*/
|
*/
|
||||||
if (c.matchedRoute?.props?.default) {
|
const routePropsOption = c.matchedRoute?.props?.default;
|
||||||
const matchedRoute = c.matchedRoute;
|
|
||||||
const routePropsOption = matchedRoute.props.default;
|
/**
|
||||||
|
* Since IonRouterOutlet renders multiple components,
|
||||||
|
* each render will cause all props functions to be
|
||||||
|
* called again. As a result, we need to cache the function
|
||||||
|
* result and provide it on each render so that the props
|
||||||
|
* are not lost when navigating from and back to a page.
|
||||||
|
* When a component is destroyed and re-created, the
|
||||||
|
* function is called again.
|
||||||
|
*/
|
||||||
|
const getPropsFunctionResult = () => {
|
||||||
|
const cachedPropsResult = c.vueComponentData?.propsFunctionResult;
|
||||||
|
if (cachedPropsResult) {
|
||||||
|
return cachedPropsResult;
|
||||||
|
} else {
|
||||||
|
const propsFunctionResult = routePropsOption(injectedRoute);
|
||||||
|
c.vueComponentData = {
|
||||||
|
...c.vueComponentData,
|
||||||
|
propsFunctionResult
|
||||||
|
};
|
||||||
|
return propsFunctionResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
const routeProps = routePropsOption
|
const routeProps = routePropsOption
|
||||||
? routePropsOption === true
|
? routePropsOption === true
|
||||||
? c.params
|
? c.params
|
||||||
: typeof routePropsOption === 'function'
|
: typeof routePropsOption === 'function'
|
||||||
? routePropsOption(matchedRoute)
|
? getPropsFunctionResult()
|
||||||
: routePropsOption
|
: routePropsOption
|
||||||
: null
|
: null
|
||||||
|
|
||||||
@ -389,7 +412,7 @@ export const IonRouterOutlet = defineComponent({
|
|||||||
...props,
|
...props,
|
||||||
...routeProps
|
...routeProps
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return h(
|
return h(
|
||||||
c.vueComponent,
|
c.vueComponent,
|
||||||
props
|
props
|
||||||
|
@ -89,14 +89,19 @@ describe('Routing', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const propsFn = jest.fn((route) => {
|
||||||
|
return { title: `${route.params.id} Title` }
|
||||||
|
});
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(process.env.BASE_URL),
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/myPath', component: Page1, props: function(route) { return { title: `${route.path} Title` } } }
|
{ path: '/myPath/:id', component: Page1, props: propsFn },
|
||||||
|
{ path: '/otherPage', component: Page1 }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push('/myPath');
|
router.push('/myPath/123');
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
const wrapper = mount(App, {
|
const wrapper = mount(App, {
|
||||||
global: {
|
global: {
|
||||||
@ -105,7 +110,19 @@ describe('Routing', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const cmp = wrapper.findComponent(Page1);
|
const cmp = wrapper.findComponent(Page1);
|
||||||
expect(cmp.props()).toEqual({ title: '/myPath Title' });
|
expect(cmp.props()).toEqual({ title: '123 Title' });
|
||||||
|
|
||||||
|
router.push('/otherPage');
|
||||||
|
await waitForRouter();
|
||||||
|
|
||||||
|
expect(propsFn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
router.back();
|
||||||
|
await waitForRouter();
|
||||||
|
|
||||||
|
expect(propsFn.mock.calls.length).toBe(1);
|
||||||
|
|
||||||
|
expect(cmp.props()).toEqual({ title: '123 Title' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass route params as props', async () => {
|
it('should pass route params as props', async () => {
|
||||||
|
Reference in New Issue
Block a user