UNPKG

react-router-decorator

Version:

基于 react-router-dom 封装的路由工具,提供装饰器/函数模式设置路由,自动排序,支持嵌套路由

437 lines (436 loc) 20.4 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "qs", "react", "react-dom", "react-router-dom", "react-router-dom"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderApp = exports.DataRouter = exports.AppRouter = exports.AppRoutes = exports.getRoutes = exports.routeSorter = exports.$page = exports.page = exports.PageWrapper = exports.DevRouterHelper = exports.getRouteConfig = exports.useSearchQuery = exports.transSearch2Query = void 0; var qs_1 = require("qs"); var React = __importStar(require("react")); var ReactDOM = __importStar(require("react-dom")); var react_router_dom_1 = require("react-router-dom"); __exportStar(require("react-router-dom"), exports); /** * 记录路由 */ var RoutesRecord = {}; var RouteWatcher = /** @class */ (function () { function RouteWatcher() { var _this = this; /** * 发布路由变化 */ this.publish = function () { _this.timeout && clearTimeout(_this.timeout); _this.timeout = setTimeout(function () { _this.subscribers.forEach(function (fn) { fn(); }); }, 10); }; /** * 订阅路由变化 * @param fn */ this.subscribe = function (fn) { _this.subscribers.push(fn); }; this.subscribers = []; } return RouteWatcher; }()); var watcher = new RouteWatcher(); var useWatcher = function () { var _a = React.useState(Object.keys(RoutesRecord).length), count = _a[0], setCount = _a[1]; React.useMemo(function () { watcher.subscribe(function () { return setCount(function (c) { return c + 1; }); }); }, []); return count; }; /** * 将地址栏 search 转换为 query 对象 */ var transSearch2Query = function (search, options) { if (search === void 0) { search = ''; } return (0, qs_1.parse)(search.replace(/^\?/, ''), options); }; exports.transSearch2Query = transSearch2Query; /** * hook 获取地址栏中的 query 对象 */ var useSearchQuery = function (options) { var _a = (0, react_router_dom_1.useLocation)().search, search = _a === void 0 ? '?' : _a; return React.useMemo(function () { return (0, exports.transSearch2Query)(location.search, options); }, [search]); }; exports.useSearchQuery = useSearchQuery; /** * 获取所有的路由绝对路径及相关配置 */ var getRouteConfig = function () { return Object.freeze(Object.keys(RoutesRecord) .filter(function (path) { if (path === void 0) { path = '/'; } return !path.endsWith('::') && !path.endsWith('*'); }) .reduce(function (prev, current) { prev[current] = __assign({}, RoutesRecord[current]); return prev; }, {})); }; exports.getRouteConfig = getRouteConfig; /** * 路由辅助工具,可渲染关键路由,方便开发人员切换页面 */ var DevRouterHelper = function (props) { var _a = props.label, label = _a === void 0 ? '快捷路由 (仅开发模式使用,鼠标移出消失、悬停上边界显示) :' : _a; var _b = React.useState(true), visible = _b[0], setVisible = _b[1]; var timeoutRef = React.useRef(); var show = React.useCallback(function () { timeoutRef.current && clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(function () { setVisible(true); }, 500); }, []); var hide = React.useCallback(function () { timeoutRef.current && clearTimeout(timeoutRef.current); setVisible(false); }, []); React.useEffect(function () { timeoutRef.current && clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(hide, 10000); return function () { return timeoutRef.current && clearTimeout(timeoutRef.current); }; }, [useWatcher()]); var routers = React.useMemo(function () { return Object.entries((0, exports.getRouteConfig)()).map(function (_a) { var path = _a[0], config = _a[1]; return (React.createElement(react_router_dom_1.Link, { key: path, to: path, style: { marginRight: 12, fontSize: 12, color: '#428df5' } }, typeof config.title === 'function' ? config.title({}, {}) : config.title)); }); }, []); return (React.createElement("div", { style: { position: 'fixed', top: 0, left: 0, right: 0, minHeight: 20, zIndex: 99999999, }, onMouseEnter: show, onMouseLeave: hide }, React.createElement("style", null), React.createElement("div", { style: { padding: '6px 12px', background: '#dfdfdf', display: 'flex', justifyContent: 'flex-start', flexWrap: 'wrap', alignItems: 'center', fontSize: 12, lineHeight: '20px', opacity: visible ? 1 : 0, transform: visible ? 'translateY(0)' : 'translateY(-100%)', transition: 'all 0.3s ease-in-out', } }, React.createElement("span", { style: { color: 'red', marginRight: 12, userSelect: 'none' } }, label), routers))); }; exports.DevRouterHelper = DevRouterHelper; /** * 默认页面 Wrapper 附加自动解析 params、query 追加 navigate * @param props * @constructor */ var PageWrapper = function (props) { var Component = props.Component, title = props.title, childrenAsOutlet = props.childrenAsOutlet, _a = props.context, context = _a === void 0 ? '' : _a, path = props.path, lazy = props.lazy; var _b = (0, react_router_dom_1.useLocation)(), _c = _b.search, search = _c === void 0 ? '?' : _c, pathname = _b.pathname; var params = (0, react_router_dom_1.useParams)(); var navigate = (0, react_router_dom_1.useNavigate)(); var query = React.useMemo(function () { return (0, exports.transSearch2Query)(search); }, [search]); var fullPath = getAbsolutePath(context, path); // 判断路由是否匹配 var matched = !!(0, react_router_dom_1.useMatch)(fullPath); // 设置 document.title React.useEffect(function () { var originalTitle = document.title; if (matched && title) { document.title = typeof title === 'function' ? title(params, query) : title; } return function () { document.title = originalTitle; }; }, [title, pathname, search, matched]); var Wrapper = lazy ? React.Suspense : React.Fragment; return (React.createElement(Wrapper, null, React.createElement(Component, { path: fullPath, params: params, query: query, navigate: navigate }, !!childrenAsOutlet && React.createElement(react_router_dom_1.Outlet, null)))); }; exports.PageWrapper = PageWrapper; /** * 路由路径统一转换为绝对路径 * @param paths */ var getAbsolutePath = function () { var paths = []; for (var _i = 0; _i < arguments.length; _i++) { paths[_i] = arguments[_i]; } return __spreadArray(['/'], paths.filter(function (path) { return Boolean(path === null || path === void 0 ? void 0 : path.trim()); }).map(function (path) { return path.trim(); }), true).join('/').replace(/\/+/g, '/') || '/'; }; /** * 路由路径正则 */ var routeRegExp = /^(\/?((:?[\w-]+\??)|(\*)))+$/i; var legalRouteRules = "/\ntest\n/test\n/:id\n/:id?\n/:name/:id\n/:name/:id?\n/:name?/:id?\n/test/*\n/:name/*\n/:name?/*\n/*\n*" .split('\n') .map(function (str) { return str.trim(); }); // legalRouteRules.map((str) => console[routeRegExp.test(str) ? 'log' : 'error'](str)); /** * 类装饰器,用于注册类组件路由 * @param path 路由路径 * @param options 附加参数 */ var page = function (path, options) { var _a = typeof options === 'string' ? { title: options } : options !== null && options !== void 0 ? options : {}, _b = _a.context, context = _b === void 0 ? '' : _b, _c = _a.index, index = _c === void 0 ? false : _c, others = __rest(_a, ["context", "index"]); // 嵌套路由上下文不合法 if (context.trim() && !context.startsWith('/')) { throw new Error('嵌套路由上下文不合法,请使用 "/" 开始的绝对路径,多级嵌套使用完整上下文'); } var absolutePath = getAbsolutePath(path); if (absolutePath !== '/' && !routeRegExp.test(path)) { throw new Error("\u8DEF\u7531\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u652F\u6301\u7684\u8DEF\u7531\u8DEF\u5F84\u5982: ".concat(legalRouteRules.map(function (p) { return "\"".concat(p, "\""); }))); } var key = getAbsolutePath(context, index ? '' : path); return function (Component) { // 如果是 index 路由,则路径会和父路径相同,增加 :: 标记防止 key 重复 RoutesRecord[index ? "".concat(key, "::") : key] = __assign(__assign({}, others), { path: absolutePath, index: index, Component: Component, context: context }); watcher.publish(); }; }; exports.page = page; /** * 函数注册路由,类组件和函数组件均可使用 * @param Component * @param path * @param options */ var $page = function (Component, path, options) { return (0, exports.page)(path, options)(Component); }; exports.$page = $page; /** * 将路由配置转换为目标对象 * @param config * @param options */ var transRoute = function (config, options) { var _a, _b, _c; var path = config.path, Component = config.Component, title = config.title, context = config.context, lazy = config.lazy, index = config.index, routeObject = __rest(config, ["path", "Component", "title", "context", "lazy", "index"]); var _withPageWrapper = (_a = Component.withPageWrapper) !== null && _a !== void 0 ? _a : options.withPageWrapper; var _childrenAsOutlet = (_b = Component.childrenAsOutlet) !== null && _b !== void 0 ? _b : options.childrenAsOutlet; var _PageWrapper = (_c = options.PageWrapper) !== null && _c !== void 0 ? _c : exports.PageWrapper; var Wrapper = lazy ? React.Suspense : React.Fragment; return __assign(__assign({}, routeObject), { index: index, path: index ? '' : path, element: _withPageWrapper ? (React.createElement(_PageWrapper, { context: context, path: path, Component: Component, title: title, childrenAsOutlet: _childrenAsOutlet, lazy: lazy })) : (React.createElement(Wrapper, null, React.createElement(Component, null, _childrenAsOutlet && React.createElement(react_router_dom_1.Outlet, null)))) }); }; /** * 路由路径排序算法 * @param a * @param b */ var routeSorter = function (a, b) { var aArr = a.split('/'); var bArr = b.split('/'); var len = Math.max(aArr.length, bArr.length); for (var i = 0; i < len; i++) { if (aArr[i] && !bArr[i]) return 1; if (!aArr[i] && bArr[i]) return -1; if (aArr[i] === bArr[i]) { continue; } // *优先级最低 if (aArr[i] === '*') return 1; if (bArr[i] === '*') return -1; return aArr[i] > bArr[i] ? 1 : -1; } return a > b ? 1 : -1; }; exports.routeSorter = routeSorter; /** * 获取路由配置 * @param options */ var getRoutes = function (options, dataRouter) { var _a = options.withPageWrapper, withPageWrapper = _a === void 0 ? true : _a, PageWrapper = options.PageWrapper, _b = options.childrenAsOutlet, childrenAsOutlet = _b === void 0 ? false : _b, helper = options.helper, _c = options.debug, debug = _c === void 0 ? false : _c; var routes = []; var root = RoutesRecord["/"], pages = __rest(RoutesRecord, ['/']); var deepContextRoute = function (ctx, routes, p) { var len = routes.length; var route; for (var i = len - 1; i >= 0; i--) { var _a = routes[i], path = _a.path, children = _a.children; var absolutePath = getAbsolutePath(p !== null && p !== void 0 ? p : '', path); if (!ctx.startsWith(absolutePath)) { continue; } if (absolutePath === ctx) { route = routes[i]; } if (!route && children) { route = deepContextRoute(ctx, children, absolutePath); } if (route) { break; } } return route; }; var getContextRoute = function (ctx) { var _ctx = routes.find(function (r) { return r.path === ctx; }); if (!_ctx) { return deepContextRoute(ctx, routes); } return _ctx; }; var transOption = { withPageWrapper: withPageWrapper, childrenAsOutlet: childrenAsOutlet, PageWrapper: PageWrapper }; root && routes.push(transRoute(root, transOption)); Object.keys(pages) .sort(exports.routeSorter) .forEach(function (p) { var option = pages[p]; var path = option.path, context = option.context, config = __rest(option, ["path", "context"]); // 有上下文则是嵌套路由 if (context) { var parent_1 = getContextRoute(context); if (parent_1 && !parent_1.children) { parent_1.children = [transRoute(__assign({ path: path.substring(1), context: context }, config), transOption)]; } else if (parent_1 === null || parent_1 === void 0 ? void 0 : parent_1.children) { parent_1.children.push(transRoute(__assign({ path: path.substring(1), context: context }, config), transOption)); } else { console.error('未找到匹配的嵌套上下文'); } } else { routes.push(transRoute(option, transOption)); } }); debug && console.warn('[react-router-decorator debug] 路由配置如下:\n\n', routes); if (dataRouter) { return [ { path: '', children: routes, element: (React.createElement(React.Fragment, null, (helper !== null && helper !== void 0 ? helper : debug) && React.createElement(exports.DevRouterHelper, null), React.createElement(react_router_dom_1.Outlet, null))), }, ]; } return routes; }; exports.getRoutes = getRoutes; /** * By use useRoutes([...]) */ var AppRoutes = function (props) { return (0, react_router_dom_1.useRoutes)(React.useMemo(function () { return (0, exports.getRoutes)(props); }, [useWatcher()])); }; exports.AppRoutes = AppRoutes; /** * Router with Routes do not support the data APIs. */ var AppRouter = function (props) { var type = props.type, _a = props.Wrapper, Wrapper = _a === void 0 ? React.Fragment : _a, helper = props.helper, others = __rest(props, ["type", "Wrapper", "helper"]); var Router = type === 'history' ? react_router_dom_1.BrowserRouter : react_router_dom_1.HashRouter; return (React.createElement(Wrapper, null, React.createElement(Router, null, (helper !== null && helper !== void 0 ? helper : props.debug) && React.createElement(exports.DevRouterHelper, null), React.createElement(exports.AppRoutes, __assign({}, others))))); }; exports.AppRouter = AppRouter; /** * Router with Routes and support the Data APIs. */ var DataRouter = function (props) { var type = props.type, _a = props.Wrapper, Wrapper = _a === void 0 ? React.Fragment : _a; var createRouter = type === 'history' ? react_router_dom_1.createBrowserRouter : react_router_dom_1.createHashRouter; var router = React.useMemo(function () { return createRouter((0, exports.getRoutes)(props, true)); }, [Object.keys(RoutesRecord).join(';')]); return (React.createElement(Wrapper, null, React.createElement(react_router_dom_1.RouterProvider, { router: router }))); }; exports.DataRouter = DataRouter; /** * By ReactDOM.render * @param element * @param options * @deprecated when use React 18. */ var renderApp = function (element, options) { ReactDOM.render(React.createElement(exports.AppRouter, __assign({}, options)), element); }; exports.renderApp = renderApp; });