react-view-router
Version:
react-view-router
501 lines • 19.5 kB
JavaScript
const _excluded = ["children"];
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import "core-js/modules/web.dom.iterable.js";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import React, { useState, useEffect } from 'react';
import { renderRoute, normalizeRoute, isFunction, isRouteChanged, isRoutesChanged, isPropChanged, nextTick, isMatchedRoutePropsChanged, getHostRouterView, warn, hasOwnProp, getRouteChildren, ignoreCatch } from './util';
import { computeRootMatch } from './match-path';
import { RouterContext, RouterViewContext } from './context';
import KeepAlive from './keep-alive';
function normalizeRouterViewProps(props) {
if (props.beforeEach && !hasOwnProp(props.beforeEach, 'global')) props.beforeEach.global = true;
if (props.afterEach && !hasOwnProp(props.afterEach, 'global')) props.afterEach.global = true;
}
export function _checkActivate(router, matchedRoute, event) {
if (!matchedRoute || !router) return;
const path1 = router.basenameNoSlash + matchedRoute.path;
const path2 = event.router.basenameNoSlash + event.target.path;
return path1 === path2;
}
export function _checkDeactivate(router, matchedRoute, event) {
if (!matchedRoute || !router) return;
const path1 = router.basenameNoSlash + matchedRoute.path;
const path2 = event.router.basenameNoSlash + event.target.path;
return path1.startsWith(path2);
}
class RouterView extends React.Component {
constructor(props) {
super(props);
_defineProperty(this, "target", void 0);
_defineProperty(this, "isRouterViewInstance", void 0);
_defineProperty(this, "_isMounted", void 0);
_defineProperty(this, "_events", void 0);
_defineProperty(this, "_reactInternalFiber", void 0);
_defineProperty(this, "_reactInternals", void 0);
_defineProperty(this, "_kaRef", void 0);
_defineProperty(this, "_isActivate", void 0);
_defineProperty(this, "_updateRef", ref => {
const {
currentRoute
} = this.state;
if (currentRoute) currentRoute.componentInstances[this.name] = ref;
if (this.props && this.props._updateRef) this.props._updateRef(ref);
if (this._kaRef?.activeNode) this._kaRef.activeNode.instance = ref;
// if (this._isMounted) this.setState({ currentRoute });
});
_defineProperty(this, "_updateKARef", ref => {
if (!ref && this._kaRef && !this.isNull(this.state.currentRoute)) {
ref = this._kaRef;
}
this._kaRef = ref;
});
_defineProperty(this, "_kaActivate", event => {
const {
parentRoute,
router
} = this.state;
if (_checkActivate(router, parentRoute, event)) {
this._isActivate = true;
this._refreshCurrentRoute();
}
});
_defineProperty(this, "_kaDeactivate", event => {
const {
parentRoute,
router
} = this.state;
if (_checkDeactivate(router, parentRoute, event)) {
this._events.deactivate.forEach(e => ignoreCatch(e)(event));
this._isActivate = false;
}
});
this.target = new.target;
this.isRouterViewInstance = true;
const _router = props && props.router;
const depth = props && props.depth ? Number(props.depth) : 0;
const state = {
_routerRoot: true,
parent: null,
depth,
inited: false,
resolving: false,
router: _router,
parentRoute: null,
currentRoute: null,
toRoute: null,
routes: [],
renderKeepAlive: false,
enableKeepAlive: false
};
this.state = state;
this._isMounted = false;
this._isActivate = true;
this._kaRef = null;
this._events = {
activate: [],
deactivate: []
};
normalizeRouterViewProps(props);
}
get name() {
const name = this.props.name;
if (!name) return 'default';
return name;
}
get currentRef() {
const currentRoute = this.state.currentRoute;
return currentRoute && currentRoute.componentInstances[this.name];
}
get isActivate() {
if (!this._isActivate) return false;
const {
_routerRoot,
router
} = this.state;
if (_routerRoot) {
if (router?.basename) {
const parent = getHostRouterView(this);
if (parent) return parent.isActivate;
}
}
return true;
}
_checkEnableKeepAlive() {
const key = 'keepAlive';
if (this._kaRef) return true;
if (hasOwnProp(this.props, key)) return true;
const {
currentRoute,
router
} = this.state;
if (hasOwnProp(currentRoute?.config, key)) return true;
const keepAliveProps = router?.options.keepAlive;
if (isFunction(keepAliveProps) || keepAliveProps instanceof RegExp) return true;
return false;
}
_filterRoutes(routes, state) {
const {
name,
filter
} = this.props;
let ret = routes && routes.filter(r => {
const hasName = name && name !== 'default';
if (r.redirect || r.index) return hasName ? name === r.name : !r.name;
return hasName ? r.components && r.components[name] : r.component || r.components && r.components.default;
});
if (filter) ret = filter(ret, state || this.state);
return ret;
}
getMatchedRoute(route, depth = 0) {
const matched = route?.matched || [];
return matched.length > depth ? matched[depth] : null;
}
isKeepAliveRoute(currentRoute, toRoute, router) {
if (!router) router = this.state.router;
if (!currentRoute) return false;
const checkKeepAlive = v => {
if (isFunction(v)) v = v(currentRoute, toRoute, {
router: router,
view: this
});
return v instanceof RegExp ? toRoute ? v.test(toRoute.path) : false : isFunction(v) ? v : Boolean(v);
};
let keepAlive = currentRoute.config.keepAlive;
if (keepAlive) return checkKeepAlive(keepAlive);
keepAlive = this.props.keepAlive;
if (keepAlive) return checkKeepAlive(keepAlive);
keepAlive = router?.options.keepAlive;
return isFunction(keepAlive) ? checkKeepAlive(keepAlive) : false;
}
_refreshCurrentRoute(state, pendingState, callback) {
if (!state) state = this.state;
const router = state.router;
if (!router) throw new Error('state.router is null!');
const currentRoute = this.state.currentRoute;
let toRoute = this.getMatchedRoute(router.currentRoute, state.depth);
if (!toRoute) {
const route = normalizeRoute({
path: ''
}, state.parentRoute && state.parentRoute.config);
toRoute = router.createMatchedRoute(route, computeRootMatch());
router.currentRoute && router.currentRoute.matched.push(toRoute);
} else if (!toRoute || toRoute.redirect) toRoute = null;
const isChanged = isRouteChanged(currentRoute, toRoute);
const isMounted = this._isMounted;
const newState = {
enableKeepAlive: this.state.enableKeepAlive || this._checkEnableKeepAlive(),
currentRoute: toRoute
};
if (toRoute) toRoute.viewInstances[this.name] = this;
if (isMounted && isChanged && router.options.keepAlive) {
const event = {
router,
source: this,
target: null,
to: toRoute,
from: currentRoute
};
newState.renderKeepAlive = this.isKeepAliveRoute(currentRoute, toRoute, router);
const kaRef = this._kaRef;
if (kaRef) {
if (newState.renderKeepAlive) {
if (currentRoute?.path === kaRef.activeName) {
event.target = currentRoute;
this._events.deactivate.forEach(e => ignoreCatch(e)(event));
const activeNode = kaRef.activeNode;
activeNode?.instance?.componentWillUnactivate && ignoreCatch(activeNode.instance.componentWillUnactivate.bind(activeNode.instance))();
}
} else if (currentRoute) {
kaRef.remove(currentRoute.path, toRoute?.path === currentRoute.path);
}
const toPath = toRoute?.path;
if (toPath && kaRef.activeName !== toPath) {
const toNode = kaRef.find(toPath);
if (toNode) {
const beforeActivate = toNode.beforeActivate || this.props.beforeActivate || router.options.beforeViewActivate;
if (!beforeActivate || beforeActivate(currentRoute, toRoute, {
view: this,
router
})) {
event.target = toRoute;
nextTick(() => {
if (!this._isMounted) return;
if (toNode.instance?.componentDidActivate) ignoreCatch(toNode.instance.componentDidActivate.bind(toNode.instance))();
this._events.activate.forEach(e => ignoreCatch(e)(event));
});
}
}
}
}
}
if (this.state.inited) {
if (pendingState) Object.assign(pendingState, newState);else if (isMounted) {
const {
currentRef
} = this;
if (isChanged && !newState.renderKeepAlive && currentRef && router._isReactViewComponent(currentRef)) {
currentRef._willUnmount();
}
try {
this.setState(newState);
} catch (ex) {
console.error(ex);
}
const {
onRouteChange
} = this.props;
if (isChanged && onRouteChange) ignoreCatch(onRouteChange)(toRoute, currentRoute);
}
} else if (state !== this.state) Object.assign(state, newState);
if (isMounted) callback && callback();
return toRoute;
}
_updateResolving(resolving, toRoute = null) {
if (!this._isMounted) return;
this.setState({
resolving,
toRoute
});
}
_resolveFallback() {
let ret = null;
const fallback = this.props.fallback;
if (isFunction(fallback)) {
const {
parentRoute,
currentRoute,
toRoute,
inited,
resolving,
depth,
router
} = this.state;
ret = fallback({
parentRoute,
currentRoute,
toRoute,
inited,
resolving,
depth,
router,
view: this
});
} else if (/*#__PURE__*/React.isValidElement(fallback)) ret = fallback;
return ret || null;
}
isNull(route) {
return !route || !route.path || route.subpath === '' || route.isNull;
}
async componentDidMount() {
this._isMounted = true;
if (this.state.inited) return;
if (!this._reactInternalFiber && !this._reactInternals) return;
const state = _objectSpread({}, this.state);
let router = state.router;
let parent = getHostRouterView(this);
const parentRouter = parent?.state.router;
if (router && parent) {
if (!parentRouter || router.mode !== parentRouter.mode || !router.basename) parent = null;
}
if (parent) {
state.parent = parent;
parent._events?.activate.push(this._kaActivate);
parent._events?.deactivate.unshift(this._kaDeactivate);
}
if (router && (!parent || parentRouter !== router)) {
if (!router.isRunning) {
warn('[RouterView]warning: router is not running.');
}
router.viewRoot = this;
const pendingRoute = router.pendingRoute;
router.pendingRoute = null;
state.routes = this._filterRoutes(router.routes);
const [, location] = router.history.getIndexAndLocation ? router.history.getIndexAndLocation() : [router.history.index, router.history.location];
router._handleRouteInterceptor(pendingRoute || _objectSpread({}, location), (ok, to) => {
if (!ok) return;
router && to && router.updateRoute(to);
this._refreshCurrentRoute(state);
if (isFunction(ok)) ok(true, router.currentRoute);
if (this._isMounted) this.setState(Object.assign(state, {
inited: this._isMounted
}));
}, true);
} else {
state._routerRoot = false;
if (!parent) {
throw new Error('[RouterView] cannot find root RouterView instance!');
}
if (!router) router = state.router = parent.state.router;
state.depth = parent.state.depth + 1;
state.parentRoute = this.getMatchedRoute(router?.currentRoute, state.depth - 1);
state.routes = state.parentRoute ? this._filterRoutes(getRouteChildren(state.parentRoute.config.children, state.parentRoute.config)) : [];
this._refreshCurrentRoute(state);
if (this._isMounted) this.setState(Object.assign(state, {
inited: true
}));
}
}
componentWillUnmount() {
this._isMounted = false;
const {
_routerRoot,
parent,
router
} = this.state;
if (parent) {
const removeEvent = (name, fn) => {
if (!parent._events) return;
const events = parent._events[name];
const idx = events.indexOf(fn);
if (~idx) events.splice(idx, 1);
};
removeEvent('activate', this._kaActivate);
removeEvent('deactivate', this._kaDeactivate);
}
_routerRoot && router && (router.viewRoot = null);
}
shouldComponentUpdate(nextProps, nextState) {
if (!this._isMounted) return false;
if (this.state.resolving !== nextState.resolving) return true;
if (this.state.inited !== nextState.inited) return true;
if (this.state.depth !== nextState.depth) return true;
if (this.state.router !== nextState.router) return true;
const router = nextState.router;
if (isPropChanged(this.props, nextProps, (key, val, oldVal) => {
if (key === 'fallback' && nextState.resolving) return false;
if (key === 'keepAlive' && isFunction(val) && isFunction(oldVal)) return false;
if (['onRouteChange', 'beforeEach', 'afterEach', 'filter', 'beforeActivate'].includes(key)) return false;
return true;
})) return true;
if (isRouteChanged(this.state.currentRoute, nextState.currentRoute)) return true;
if (isRoutesChanged(this.state.routes, nextState.routes)) return true;
if (router && isMatchedRoutePropsChanged(this.state.currentRoute, router, this.name)) return true;
return false;
}
static getDerivedStateFromProps(nextProps) {
normalizeRouterViewProps(nextProps);
return null;
}
getComponentProps() {
const _this$props = this.props,
{
children
} = _this$props,
props = _objectWithoutProperties(_this$props, _excluded);
const excludeProps = this.target.defaultProps.excludeProps || RouterView.defaultProps.excludeProps || [];
excludeProps.forEach(key => delete props[key]);
return {
props,
children
};
}
getComponent(currentRoute) {
if (!currentRoute) return null;
const {
routes,
router
} = this.state;
const {
query = {},
params = {}
} = router && router.currentRoute || {};
const {
children,
props
} = this.getComponentProps();
return renderRoute(currentRoute, routes, props, children, {
router,
name: this.name,
query,
params,
ref: this._updateRef
});
}
renderCurrent(currentRoute) {
if (this.isNull(currentRoute)) return this.props.children || null;
return this.getComponent(currentRoute);
}
renderContainer(current, currentRoute) {
const {
routes,
router,
depth
} = this.state;
let {
container
} = this.props;
if (router) {
container = router._callEvent('onViewContainer', container, {
routes,
route: currentRoute,
depth,
router,
view: this
}) || container;
}
return container && currentRoute ? container(current, currentRoute, current && current.props || this.getComponentProps(), this) : current;
}
render() {
if (!this.state.inited) return this._resolveFallback();
const {
router
} = this.state;
if (!router) return null;
const {
currentRoute,
_routerRoot,
resolving,
renderKeepAlive,
enableKeepAlive
} = this.state;
const renderUtils = router.options.renderUtils;
let ret = this.renderCurrent(currentRoute);
if (enableKeepAlive && renderUtils) {
const activeName = currentRoute ? currentRoute.path : '';
const extra = {};
if (isFunction(renderKeepAlive)) extra.beforeActivate = renderKeepAlive;
ret = /*#__PURE__*/React.createElement(KeepAlive, Object.assign({
utils: renderUtils,
activeName,
anchorName: `${router.mode}:${router.basenameNoSlash}:${activeName}`,
ref: this._updateKARef,
extra
}), ret);
}
ret = this.renderContainer(ret, currentRoute);
ret = /*#__PURE__*/React.createElement(RouterViewContext.Provider, {
value: this
}, ret);
if (_routerRoot) {
ret = /*#__PURE__*/React.createElement(RouterContext.Provider, {
value: router
}, ret);
}
ret = /*#__PURE__*/React.createElement(React.Fragment, {}, ret, resolving ? this._resolveFallback() : null);
return ret;
}
}
_defineProperty(RouterView, "defaultProps", void 0);
const RouterViewWrapper = /*#__PURE__*/React.forwardRef((props, ref) => {
const [isRunning, setIsRunning] = useState(!props.router || props.router.isRunning);
useEffect(() => {
if (!isRunning && props.router && props.router.isRunning) {
setIsRunning(true);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[isRunning, props.router && props.router.isRunning]);
return isRunning ? /*#__PURE__*/React.createElement(RouterView, _objectSpread(_objectSpread({}, props), {}, {
_updateRef: ref && (isFunction(ref) ? ref : r => ref.current = r)
})) : null;
});
RouterView.defaultProps = {
excludeProps: ['_updateRef', 'name', 'filter', 'fallback', 'container', 'router', 'depth', 'excludeProps', 'beforeEach', 'afterEach', 'onRouteChange', 'keepAlive']
};
export { RouterViewWrapper, RouterView as RouterViewComponent };
export default RouterViewWrapper;