feat(react): React Router Enhancements (#21693)

This commit is contained in:
Ely Lucas
2020-07-07 11:02:05 -06:00
committed by GitHub
parent a0735b97bf
commit c171ccbd37
245 changed files with 26872 additions and 1126 deletions

View File

@ -0,0 +1,178 @@
import { IonRoute, RouteInfo, ViewItem, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
import React from 'react';
import { matchPath } from 'react-router';
export class ReactRouterViewStack extends ViewStacks {
constructor() {
super();
this.createViewItem = this.createViewItem.bind(this);
this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this);
this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
this.getChildrenToRender = this.getChildrenToRender.bind(this);
this.getViewItemForTransition = this.getViewItemForTransition.bind(this);
}
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
const viewItem: ViewItem = {
id: generateId('viewItem'),
outletId,
ionPageElement: page,
reactElement,
mount: true,
ionRoute: false
};
const matchProps = {
exact: reactElement.props.exact,
path: reactElement.props.path || reactElement.props.from,
component: reactElement.props.component
};
const match = matchPath(routeInfo.pathname, matchProps);
if (reactElement.type === IonRoute) {
viewItem.ionRoute = true;
viewItem.disableIonPageManagement = reactElement.props.disableIonPageManagement;
}
viewItem.routeData = {
match,
childProps: reactElement.props
};
return viewItem;
}
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
const viewItems = this.getViewItemsForOutlet(outletId);
// Sync latest routes with viewItems
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
const viewItem = viewItems.find(v => {
return matchComponent(child, v.routeData.childProps.path || v.routeData.childProps.from);
});
if (viewItem) {
viewItem.reactElement = child;
}
});
const children = viewItems.map(viewItem => {
let clonedChild;
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
clonedChild = (
<ViewLifeCycleManager key={`view-${viewItem.id}`} mount={viewItem.mount} removeView={() => this.remove(viewItem)}>
{React.cloneElement(viewItem.reactElement, {
computedMatch: viewItem.routeData.match
})}
</ViewLifeCycleManager>
);
} else {
const match = matchComponent(viewItem.reactElement, routeInfo.pathname);
clonedChild = (
<ViewLifeCycleManager key={`view-${viewItem.id}`} mount={viewItem.mount} removeView={() => this.remove(viewItem)}>
{React.cloneElement(viewItem.reactElement, {
computedMatch: viewItem.routeData.match
})}
</ViewLifeCycleManager>
);
if (!match && viewItem.routeData.match) {
viewItem.routeData.match = undefined;
viewItem.mount = false;
}
}
return clonedChild;
});
return children;
}
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
if (viewItem && match) {
viewItem.routeData.match = match;
}
return viewItem;
}
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, true);
return viewItem;
}
getViewItemForTransition(pathname: string) {
const { viewItem } = this.findViewItemByPath(pathname, undefined, true, true);
return viewItem;
}
private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
let viewItem: ViewItem | undefined;
let match: ReturnType<typeof matchPath> | undefined;
let viewStack: ViewItem[];
if (outletId) {
viewStack = this.getViewItemsForOutlet(outletId);
viewStack.some(matchView);
if (!viewItem) {
viewStack.some(matchDefaultRoute);
}
} else {
const viewItems = this.getAllViewItems();
viewItems.some(matchView);
if (!viewItem) {
viewItems.some(matchDefaultRoute);
}
}
return { viewItem, match };
function matchView(v: ViewItem) {
if (mustBeIonRoute && !v.ionRoute) {
return false;
}
const matchProps = {
exact: forceExact ? true : v.routeData.childProps.exact,
path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component
};
const myMatch = matchPath(pathname, matchProps);
if (myMatch) {
viewItem = v;
match = myMatch;
return true;
}
return false;
}
function matchDefaultRoute(v: ViewItem) {
// try to find a route that doesn't have a path or from prop, that will be our default route
if (!v.routeData.childProps.path && !v.routeData.childProps.from) {
match = {
path: pathname,
url: pathname,
isExact: true,
params: {}
};
viewItem = v;
return true;
}
return false;
}
}
}
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
const matchProps = {
exact: forceExact ? true : node.props.exact,
path: node.props.path || node.props.from,
component: node.props.component
};
const match = matchPath(pathname, matchProps);
return match;
}