fix(vue): pass in correct route to props function (#22605)

resolves #22602
This commit is contained in:
Liam DeBeasi
2020-12-07 10:33:22 -05:00
committed by GitHub
parent 36939e10ae
commit 01afdc42e5
4 changed files with 69 additions and 19 deletions

View File

@ -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 {

View File

@ -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: {}
}; };
} }

View File

@ -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,22 +376,43 @@ 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;
const routeProps = routePropsOption
? routePropsOption === true
? c.params
: typeof routePropsOption === 'function'
? routePropsOption(matchedRoute)
: routePropsOption
: null
props = { /**
...props, * Since IonRouterOutlet renders multiple components,
...routeProps * 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
? routePropsOption === true
? c.params
: typeof routePropsOption === 'function'
? getPropsFunctionResult()
: routePropsOption
: null
props = {
...props,
...routeProps
}
return h( return h(
c.vueComponent, c.vueComponent,
props props

View File

@ -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 () => {