react-router-decorator
Version:
基于 react-router-dom 封装的路由工具,提供装饰器/函数模式设置路由,自动排序,支持嵌套路由
437 lines (436 loc) • 20.4 kB
JavaScript
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;
});