Skip to content

Commit 24659a5

Browse files
authoredOct 14, 2021
fix(vue): mount correct views when navigating (#24056)
resolves #23914
1 parent a09d7d4 commit 24659a5

File tree

6 files changed

+125
-39
lines changed

6 files changed

+125
-39
lines changed
 

‎packages/vue-router/__tests__/viewStacks.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('View Stacks', () => {
110110
const itemC = createRegisteredViewItem(viewStacks, 1, '/home/3', true);
111111
const itemD = createRegisteredViewItem(viewStacks, 1, '/home/4', true);
112112

113-
viewStacks.unmountLeavingViews(1, itemA, itemD);
113+
viewStacks.unmountLeavingViews(1, itemA, -3);
114114

115115
expect(itemB.mount).toEqual(false);
116116
expect(itemB.ionPageElement).toEqual(undefined);
@@ -127,7 +127,7 @@ describe('View Stacks', () => {
127127
const itemC = createRegisteredViewItem(viewStacks);
128128
const itemD = createRegisteredViewItem(viewStacks);
129129

130-
viewStacks.mountIntermediaryViews(1, itemD, itemA);
130+
viewStacks.mountIntermediaryViews(1, itemA, 3);
131131

132132
expect(itemB.mount).toEqual(true);
133133
expect(itemC.mount).toEqual(true);

‎packages/vue-router/src/router.ts

+1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
262262
}
263263

264264
routeInfo.position = currentHistoryPosition;
265+
routeInfo.delta = delta;
265266
const historySize = locationHistory.size();
266267
const historyDiff = currentHistoryPosition - initialHistoryPosition;
267268

‎packages/vue-router/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface RouteInfo {
2727
pushedByRoute?: string;
2828
tab?: string;
2929
position?: number;
30+
delta?: number;
3031
}
3132

3233
export interface RouteParams {

‎packages/vue-router/src/viewStacks.ts

+18-34
Original file line numberDiff line numberDiff line change
@@ -164,48 +164,20 @@ export const createViewStacks = (router: Router) => {
164164
return [];
165165
}
166166

167-
/**
168-
* Given a view stack and entering/leaving views,
169-
* determine the position of each item in the stack.
170-
* This is useful for removing/adding views in between
171-
* the view items when navigating using router.go.
172-
* Use this method instead of doing an `Array.findIndex`
173-
* for both view items.
174-
*/
175-
const findViewIndex = (viewStack: ViewItem[], enteringViewItem: ViewItem, leavingViewItem: ViewItem) => {
176-
let enteringIndex = -1;
177-
let leavingIndex = -1;
178-
179-
for (let i = 0; i <= viewStack.length - 1; i++) {
180-
const viewItem = viewStack[i];
181-
if (viewItem === enteringViewItem) {
182-
enteringIndex = i;
183-
} else if (viewItem === leavingViewItem) {
184-
leavingIndex = i;
185-
}
186-
187-
if (enteringIndex > -1 && leavingIndex > -1) {
188-
break;
189-
}
190-
}
191-
192-
return { enteringIndex, leavingIndex };
193-
}
194-
195167
/**
196168
* When navigating backwards, we need to clean up and
197169
* leaving pages so that they are re-created if
198170
* we ever navigate back to them. This is especially
199171
* important when using router.go and stepping back
200172
* multiple pages at a time.
201173
*/
202-
const unmountLeavingViews = (outletId: number, enteringViewItem: ViewItem, leavingViewItem: ViewItem) => {
174+
const unmountLeavingViews = (outletId: number, viewItem: ViewItem, delta: number = 1) => {
203175
const viewStack = viewStacks[outletId];
204176
if (!viewStack) return;
205177

206-
const { enteringIndex: startIndex, leavingIndex: endIndex } = findViewIndex(viewStack, enteringViewItem, leavingViewItem);
178+
const startIndex = viewStack.findIndex(v => v === viewItem);
207179

208-
for (let i = startIndex + 1; i < endIndex; i++) {
180+
for (let i = startIndex + 1; i < startIndex - delta; i++) {
209181
const viewItem = viewStack[i];
210182
viewItem.mount = false;
211183
viewItem.ionPageElement = undefined;
@@ -219,14 +191,26 @@ export const createViewStacks = (router: Router) => {
219191
* developers to step forward over multiple views.
220192
* The intermediary views need to be remounted so that
221193
* swipe to go back works properly.
194+
* We need to account for the delta value here too because
195+
* we do not want to remount an unrelated view.
196+
* Example:
197+
* /home --> /page2 --> router.back() --> /page3
198+
* Going to /page3 would remount /page2 since we do
199+
* not prune /page2 from the stack. However, /page2
200+
* needs to remain in the stack.
201+
* Example:
202+
* /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2)
203+
* We would end up on /page3, but users need to be able to swipe
204+
* to go back to /page2 and /home, so we need both pages mounted
205+
* in the DOM.
222206
*/
223-
const mountIntermediaryViews = (outletId: number, enteringViewItem: ViewItem, leavingViewItem: ViewItem) => {
207+
const mountIntermediaryViews = (outletId: number, viewItem: ViewItem, delta: number = 1) => {
224208
const viewStack = viewStacks[outletId];
225209
if (!viewStack) return;
226210

227-
const { enteringIndex: endIndex, leavingIndex: startIndex } = findViewIndex(viewStack, enteringViewItem, leavingViewItem);
211+
const startIndex = viewStack.findIndex(v => v === viewItem);
228212

229-
for (let i = startIndex + 1; i < endIndex; i++) {
213+
for (let i = startIndex + 1; i < startIndex + delta; i++) {
230214
viewStack[i].mount = true;
231215
}
232216
}

‎packages/vue/src/components/IonRouterOutlet.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export const IonRouterOutlet = defineComponent({
214214

215215
const handlePageTransition = async () => {
216216
const routeInfo = ionRouter.getCurrentRouteInfo();
217-
const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname } = routeInfo;
217+
const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname, delta } = routeInfo;
218218

219219
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id, usingDeprecatedRouteSetup);
220220
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id, true, usingDeprecatedRouteSetup);
@@ -286,10 +286,10 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
286286
leavingViewItem.mount = false;
287287
leavingViewItem.ionPageElement = undefined;
288288
leavingViewItem.ionRoute = false;
289-
viewStacks.unmountLeavingViews(id, enteringViewItem, leavingViewItem);
289+
viewStacks.unmountLeavingViews(id, enteringViewItem, delta);
290290
}
291291
} else {
292-
viewStacks.mountIntermediaryViews(id, enteringViewItem, leavingViewItem);
292+
viewStacks.mountIntermediaryViews(id, leavingViewItem, delta);
293293
}
294294

295295
fireLifecycle(leavingViewItem.vueComponent, leavingViewItem.vueComponentRef, LIFECYCLE_DID_LEAVE);

‎packages/vue/test-app/tests/unit/routing.spec.ts

+100
Original file line numberDiff line numberDiff line change
@@ -441,4 +441,104 @@ describe('Routing', () => {
441441

442442
expect(beforeRouteEnterSpy).toHaveBeenCalledTimes(2);
443443
});
444+
445+
it('should not mount intermediary components when delta is 1', async () => {
446+
const Page = {
447+
components: { IonPage },
448+
template: `<ion-page></ion-page>`
449+
}
450+
const Page2 = {
451+
components: { IonPage },
452+
template: `<ion-page></ion-page>`
453+
}
454+
const Page3 = {
455+
components: { IonPage },
456+
template: `<ion-page></ion-page>`
457+
}
458+
459+
const router = createRouter({
460+
history: createWebHistory(process.env.BASE_URL),
461+
routes: [
462+
{ path: '/page', component: Page },
463+
{ path: '/page2', component: Page2 },
464+
{ path: '/page3', component: Page3 },
465+
{ path: '/', redirect: '/page' }
466+
]
467+
});
468+
469+
router.push('/');
470+
await router.isReady();
471+
const wrapper = mount(App, {
472+
global: {
473+
plugins: [router, IonicVue]
474+
}
475+
});
476+
477+
expect(wrapper.findComponent(Page).exists()).toBe(true);
478+
479+
router.push('/page2');
480+
await waitForRouter();
481+
482+
expect(wrapper.findComponent(Page2).exists()).toBe(true);
483+
484+
router.back();
485+
await waitForRouter();
486+
487+
expect(wrapper.findComponent(Page2).exists()).toBe(false);
488+
489+
router.push('/page3');
490+
await waitForRouter();
491+
492+
expect(wrapper.findComponent(Page2).exists()).toBe(false);
493+
expect(wrapper.findComponent(Page3).exists()).toBe(true);
494+
});
495+
496+
it('should unmount intermediary components when using router.go', async () => {
497+
const Page = {
498+
components: { IonPage },
499+
template: `<ion-page></ion-page>`
500+
}
501+
const Page2 = {
502+
components: { IonPage },
503+
template: `<ion-page></ion-page>`
504+
}
505+
const Page3 = {
506+
components: { IonPage },
507+
template: `<ion-page></ion-page>`
508+
}
509+
510+
const router = createRouter({
511+
history: createWebHistory(process.env.BASE_URL),
512+
routes: [
513+
{ path: '/page', component: Page },
514+
{ path: '/page2', component: Page2 },
515+
{ path: '/page3', component: Page3 },
516+
{ path: '/', redirect: '/page' }
517+
]
518+
});
519+
520+
router.push('/');
521+
await router.isReady();
522+
const wrapper = mount(App, {
523+
global: {
524+
plugins: [router, IonicVue]
525+
}
526+
});
527+
528+
router.push('/page2');
529+
await waitForRouter();
530+
531+
router.push('/page3');
532+
await waitForRouter();
533+
534+
expect(wrapper.findComponent(Page2).exists()).toBe(true);
535+
expect(wrapper.findComponent(Page3).exists()).toBe(true);
536+
537+
router.go(-2);
538+
await waitForRouter();
539+
540+
expect(wrapper.findComponent(Page).exists()).toBe(true);
541+
expect(wrapper.findComponent(Page2).exists()).toBe(false);
542+
expect(wrapper.findComponent(Page3).exists()).toBe(false);
543+
});
444544
});

0 commit comments

Comments
 (0)
Please sign in to comment.