UNPKG

react-view-router

Version:
501 lines 19.5 kB
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;