欢迎您开始@medux 之旅,建议您依次阅读如下 4 篇文章,这将耗费您大约 30 分钟。javascript
第 3 篇:medux 路由java
-- Github 地址 ---react
上篇阐述了 medux 路由的基本思路,提到 medux 将路由及参数视为另外一种 Store,它跟 Redux 的 Store 同样影响着 UI 的展现,并且 medux 建议您在编写 component 时忘掉路由的概念,下面结合一个具体实现方案 @medux/route-plan-a 详细解释一下:git
@medux/route-plan-a 是一套基于 @medux/core 实现的跨平台路由方案,它能够将 web 的路由风格带入其它平台github
一般路由解析及 history 功能由宿主平台提供,不一样平台的路由方案不尽相同,medux 定义了统一通用的路由数据结构 RouteData
,框架将自动把 原生路由信息
转换为 medux 使用的RouteData
web
interface RouteData {
// views 表示当前路由下要展现哪些view
views: {[moduleName: string]: {[viewName: string]: boolean}};
// paths 表示当前路由下view的嵌套父子关系
paths: string[];
// params 表示当前路由保存(传递)了哪些状态
params: {[moduleName: string]: {[key: string]: any}};
// 当原生路由支持多history栈时,每一个栈上分别保存(传递)了哪些状态
// 常见的web或是小程序都只支持一个history栈,APP支持多栈
stackParams: {[moduleName: string]: {[key: string]: any}}[];
}
复制代码
咱们把宿主原生路由信息命名为 Location,例如在 web 环境中:typescript
interface BrowserLocation {
pathname: string;
search: string;
hash: string;
state: any;
}
复制代码
因为其中的 state 可能包含反作用,因此本方案将其排除,也就是说在本方案中“你不能使用浏览器的 state 来存放数据”,因此请忘掉 state 吧:json
interface MeduxLocation {
pathname: string;
search: string;
hash: string;
}
复制代码
有的时候,咱们须要基于当前 RouteData 并修改其中的某些值来建立一个 RouteData,这种状况下咱们能够简化 RouteData 的书写:小程序
interface RoutePayload<P> {
extend?: RouteData; // 基于一个RouteData
params?: DeepPartial<P>; //修改其中的某些值
paths?: string[]; //修改其中的paths
}
复制代码
本方案建立了一个转换器,在 Location 和 RouteData 之间转换:api
interface TransformRoute {
locationToRoute: (location: MeduxLocation) => RouteData;
routeToLocation: (routeData: RouteData) => MeduxLocation;
}
复制代码
咱们都很熟悉 Web 中的历史记录操做,好比有 push、replace 等,既然 Location 和 RouteData 能够相互转换,那么相应的咱们能够更灵活的使用它们:
interface HistoryActions<P = {}> {
listen(listener: LocationListener): UnregisterCallback;
getLocation(): MeduxLocation;
getRouteData(): RouteData;
push(data: RoutePayload | MeduxLocation | string): void;
replace(data: RoutePayload | MeduxLocation | string): void;
go(n: number): void;
goBack(): void;
goForward(): void;
}
复制代码
咱们建立了一个方法直接将 RoutePayload 生成 url:
interface ToBrowserUrl {
(routeOptions: RoutePayload): string;
(pathname: string, search: string, hash: string): string;
}
复制代码
说了这么多,咱们将 Location 与 RouteData 转换的规则是什么呢?这就是路由配置文件:
interface RouteConfig {
[path: string]: string | [string, RouteConfig];
}
复制代码
代码示例:
const routeConfig = {
'/$': '@./admin/home', //$结尾为精确匹配,@开头表示redirect
'/': [
'app.Main',
{
'/login': 'app.LoginPage',
'/admin$': '@./admin/home', //$结尾为精确匹配,@开头表示redirect
'/admin': [
'adminLayout.Main',
{
'/admin/home': 'adminHome.Main',
'/admin/role/:listView': [
'adminRole.List',
{
'/admin/role/:listView/:itemView/:itemId': 'adminRole.Detail',
},
],
},
],
'/article$': '@./article/home',
'/article': [
'articleLayout.Main',
{
'/article/home': 'articleHome.Main',
'/article/about': 'articleAbout.Main',
},
],
},
],
};
复制代码
RouteConfig 是一个递归对象,它的 key 表示匹配规则 rule,对应的值为 viewName 或者数组 [viewName, RouteConfig]
假设当前 url 为 /admin/role/list
根据以上配置规则,能够解析得出 RouteData:
{
"views": {
"app": {"Main": true},
"adminLayout": {"Main": true},
"adminRole": {"List": true}
},
"paths": ["app.Main", "adminLayout.Main", "adminRole.List"],
"params": {
"app": {},
"adminLayout": {},
"adminRole": {"listView": "list"}
}
}
复制代码
以上示例子中 params 参数:adminRole: {listView: "list"} 来自于 pathname 对 rule /admin/role/:listView
的匹配,因此 pathname 中能够传递参数,它们会被提取到 params 中,而 params 则会以 moduleName 做为命名空间。
那若是将 RouteConfig 中规则 /admin/role/:listView
改成 /admin/role/:listView.name
,解析后你会看到这样的变化:
adminRole: {listView: "list"} 变成了 adminRole: {listView: {name: "list"}}
也就是说 path 中不只能够传递的参数,还能够结构化,能够多层级。
利用 pathname 只能传递简单的 string 参数。咱们知道一般 url 中传递参数是利用 search,好比 /admin/role/list?title=medux&page=1&pagesize=20
在本方案中咱们也能够利用 search 来传递复杂参数,只不过是直接将 json 字符串放入 search 参数中,好比:
/admin/role/list?q={adminRole: {title: "medux", page: 1, pageSize: 20}}
本方案中 hash 也跟 search 同样能够传递复杂参数,可是因为它不会被浏览器发送到服务器,因此咱们专门用来存储一些带下划线私有参数,例如:
/admin/role/list?q={adminRole: {title: "medux", page: 1, pageSize: 20}}#q={adminRole: {_random: 34532324}}
_
前缀咱们还能够为每一个 module 预先定义一组参数的默认值,好比:
{
"adminRole": {
"page": 1,
"pageSize": 20,
"sortBy": "createTime"
}
}
复制代码
因此依据本方案,pathname、search、hash、defaultParams 均可以传递结构化的参数,最终它们会被合并放入 RouteData 的 params 中,因此最终你能够看到的 RouteData 以下
{
"views": {
"app": {"Main": true},
"adminLayout": {"Main": true},
"adminRole": {"List": true}
},
"paths": ["app.Main", "adminLayout.Main", "adminRole.List"],
"params": {
"app": {},
"adminLayout": {},
"adminRole": {
"listView": "list",
"title": "medux",
"page": 1,
"pageSize": 20,
"sortBy": "createTime",
"_random": 34532324
}
}
}
复制代码
以上阐述的是怎么将一个 Location 转换为 RouteData,那么反过来天然也可将 RouteData 转换为 Location。
_
前缀的数据项会自动放入 hash 中const url = toBrowserUrl({
paths: ['app.Main', 'adminLayout.Main', 'adminRole.List'],
params: {
adminRole: {
listView: 'list', //被pathname匹配到的数据项会放入pathname
title: 'medux',
page: 1, //与默认参数相同的数据项将会被排除
pageSize: 20, //与默认参数相同的数据项将会被排除
sortBy: 'createTime', //与默认参数相同的数据项将会被排除
_random: 34532324, //带_的数据项将放入hash中
},
},
});
复制代码
因此以上代码将获得:
/admin/role/list?q={adminRole: {title: "medux"}}#q={adminRole: {_random: 34532324}}
能够看到咱们的 RouteData 中的 params 都是以 moduleName 做为命名空间的,由于咱们原本就但愿将 Route 视为一个 Store。如今让咱们把 RouteState 合并到 ReduxState 中,并将 params 注入到 moduleState 中,最终的 RootState 多是这样
{
route: {
location: {
pathname: "/admin/role/list",
search: '?q={adminRole: {title: "medux"}}',
hash: '#q={adminRole: {_random: 34532324}}'
},
data: {
views: {
app: {Main: true},
adminLayout: {Main: true},
adminRole: {List: true},
},
paths: ['app.Main','adminLayout.Main','adminRole.List'],
params: {
app: {},
adminLayout: {},
adminRole: {
listView: "list",
title: "medux",
page: 1,
pageSize: 20,
sortBy: "createTime",
_random: 34532324
}
}
}
},
app: {
routeParams: {},
...
},
adminLayout: {
routeParams: {},
...
},
adminRole: {
routeParams: { // 已经将RouteState中的状态注入到对应的模块中
listView: "list",
title: "medux",
page: 1,
pageSize: 20,
sortBy: "createTime",
_random: 34532324
},
...
}
}
复制代码
那么此时,你在 Component 里面使用 moduleState 时已经不须要思考它的来源是哪里了,也许是路由解析得出的,但也没准是 dispatch action 获得的呢。
medux-react-admin:基于@medux/react-web-router
和最新的ANTD 4.x
开发的通用后台管理系统,除了演示 medux 怎么使用,它还创造了很多独特的理念