微信小程序路由实践:Navigator模块、Typescript + Router
1.简介
微信小程序由一个实例App()
和多个Page()
组成。在小程序中,所有页面的路由都由框架来处理。框架将所有页面维护为堆栈,然后提供以下 API 在路由之间跳转:
wx.navigateTo
wx。 redirectTo
wx.navigateBack
wx.switchTab
wx.reLaunch
但是,对于业务应用程序,这些问题留给开发人员:
- 本机 API 使用
Callback 函数实现形式类似于我们现代常见的
Promise
和async/await
gap存在。 - 基于小程序的路由设计,将真实路由暴露在外(如扫码、链接公众号等),为后期项目重构留下历史包袱。
- 一组小程序页面最多可以有十个级别。经过十多个阶段后,
wx.navigateTo
失效,开发者应自行判断,使用wx.redirectTo
或其他小程序页面API - 。集合中有一个特殊的页面:标签页,需要使用
wx.switchTab
进行跳转。需要开发者主动判断,而且以后修改标签页属性也不方便。 - 此外,小程序代码必须使用wxacode.getUnlimited API,并且参数长度限制为32位。开发商必须自己解决这个问题。
本文希望能为这些问题一一提供解决方案。 ? 我们的想法是设计一个逻辑,根据场景自动判断使用哪个微信路由API,然后只对外提供一个功能,例如:
gotoPage('/pages/goods/index')
具体逻辑如下:
- 重定向路径时 使用小组件标签页时,请使用
wx.switchTab
。 - 当页面堆栈达到10层时,如果要跳转的页面在页面堆栈中,使用
wx.navigateBack({ delta: X })
打开堆栈到目标页面。 - 当页面集达到10级时,页面集中不存在目标页面,请使用
wx.redirectTo
替换页面集的首页页面。 - 其他情况下,使用
wx.navigateTo
顺便说一句,我们将这个函数实现为 Promise,并支持以 object
的形式传递参数,例如:
gotoPage('/pages/goods/index', { name: 'jc' }).then(...).catch(...);
大多数情况下,只需使用gotoPage
即可满足。
在某些情况下,您肯定需要明确指定使用 navigateTo/switchTab/redirectTo/navigateBack
中的哪一个。
然后我们也遵循类似的实现方式,满足相同的API模式
navigateTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); switchTab('/pages/goods/index', { name: 'jc' }).then(...).catch(...); redirectTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); navigateBack('/pages/goods/index', { name: 'jc' }).then(...).catch(...);
这些功能可以连接在同一个模块中,我们称之为:Navigator
const navigator = new Navigator(); navigator.gotoPage(...); navigator.navigateTo(...); navigator.switchTab(...); navigator.redirectTo(...); navigator.navigateBack(...);
模块设计:
3.虚拟路由策略-路由器模块
这里解决:
- 真实路径暴露在外界,造成很多历史包袱。
在开发很多应用程序时,我们经常需要将所有匹配某种模式的路由映射到同一页面。
例如,我们有一个产品页面,用于托管具有不同 ID 的所有产品。
所以在代码层面,期望实现这样的方法调用:
// 创建路由实例 const router = new Router(); // 注册路由 router.register({ path: '/goods/:id', // 虚拟路由 route: '/pages/goods/index', // 真实路由 }); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '123' } router.gotoPage('/goods/123'); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '456' } router.gotoPage('/goods/456');
类路由器 基本逻辑是完成:
- 路由注册,完成“虚拟路径”与“实际路径”存储。
- 满足“虚拟路径”到“真实路径”的转换,识别“动态路径参数”(动态段)。
- 路线跳转。
对于“路线注册”,我们可以通过将地图保存在其中来完成。
关于“路由转换”,vue-router
有类似的实现。通过查看源码,我们发现它内部使用了正则表达式路径作为我们可以使用的路径匹配机制。
那么我们就可以直接复用上述模块Navigator来“跳转路线”,通过输入正确的路线来完成路线跳转。
模块设计:
其中:
- RouteMatcher:提供动态路由参数匹配功能,内部使用正则表达式路由作为路由匹配机制。
- 路由:为每条路由创建一个路由器,并存储每条路由的虚拟路由与实际路由的比例。
- 路由器:集成内部模块,提供统一优雅的对外调用方式。
4。土地转让策略-土地转让模块
这里解决:
- 小程序扫描,公众号链接等场景落地页的统一。
- Wxacode.getUnlimited API minicode 打破了 32 位参数长度限制。
4.1。针对第一个问题要解决:单一落地页
我们称之为扫描小程序码打开小程序页面的方式、公众号菜单、公众号文章等。对于“外部路由”。
根据小程序的设计,对外暴露的链接都是真实的页面路径,如:/pages/home/index
。这种设计在实践中的缺点是: 各个登陆页面比较分散,后期很难更改正确的文件路径。
就产品“中长生命周期”而言,重复产品必然导致项目重构。如果所有实际的分布式路径都是原始的,那么我们在改造的时候就会手足无措,不得不进行很多兼容操作。因为你不知道实体材料上印有多少个分布式小程序二维码。
那么策略“虚拟路由”+“落地转运”就显得基础又重要了。
“虚拟路由”功能由路由器模块支持。我们还需要为外界提供单一的登陆页面来完成内部路由的下载。
基本逻辑:
- 分配的真实路径指向单个着陆页,如:
$LAND_PAGE: /pages/land-page/index
- 从该着陆页完成内部路由重定向通过接受这样的参数进行转发:
path=/user&name=jc&age=18
在代码层面,我们希望实现这样的用法:
// /pages/land-page/index.ts const landTransfer = new LandTransfer(landTransferOptions); Page({ onLoad(options) { landTransfer .run(options) .then(() => {...}) .catch(() => {...}); } });
然后我们还可以使用装饰器进行 TS 的变体,更简单:
import { landTransferDecorator } from 'wxapp-router'; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, });
4.2。另外要解决的问题是:短链参数
微信小程序主要提供了两个生成迷你码的接口:
- wxacode.get:获取迷你码,适合需要少量代码的业务场景。 通过该接口生成的迷你码永久有效,数量限制为100,000块
- wxacode.getUnlimited:获取迷你码,适合需要极大数量代码的业务场景。 通过该接口生成的小程序代码永久有效,目前数量不限。
第一种方法,wxacode.get
,限量10万件。虽然金额很大,但大多数小程序可能都花不完。
但是,如果我们管理一个中型到大型的电商小程序,如果:10000个产品×10个产品规格,就会超过这个数量。那么转型就很难进行。
所以,如果我们管理一个产品“中长生命周期”,我们会采用另一种方法:wxacode.getUnlimited
令人不满意的是,虽然没有数量限制,但是有参数限制为32个字符,这显然是不够的(uuid有32个字符)。
对于这种情况,我们可以使用“短链参数”的形式来解决问题。由于wxacode.getUnlimited会将scene
字段作为查询参数传递给小程序,那么我们可以传递scene
参数来运行短链服务,这需要后台配合。
前后端交互如下:
- 当小程序需要生成小程序代码时,会请求后端提供的接口,例如:
/api/encodeShortParams
- 后端将内容转换为32个字符的字符串,存储在数据库中。
- 后端使用wxacode.getUnlimited接口使用短字符串作为
场景
值,并使用协商的统一登陆页面$LAND_PAGE
作为页面
值,以编程方式创建一个小的代码。 - 通过小程序代码进入小程序时,小程序获取参数
场景
,请求后端提供的接口,例如:/api/decodeShrotParams
- 小程序理解内容并跳转到登陆页面。
对于统一落地页的前端逻辑处理,我们只需要在第一个问题的基础上添加转换短链参数的逻辑即可:
处代码层面,我们只需要定义更多的方法来转换短链参数链:convertScenePrams
// in /pages/land-page/index.js import { landTransferDecorator } from 'wxapp-router'; const landTransferOptions = { // 此处接收 onLoad(options) 中的 options.scene convertSceneParams: (sceneParams) => { return API.convertScene({ sceneParams }).then((content) => { // 假如后端存的是 JSON 字符串,前端decode // 要求 content = { path: '/home', a: 1, b:2 } return JSON.parse(content); }); }, }; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, });
以及它们之间的API.convertScene
通过连接服务器并提供HTTP接口服务来完成。
4.3。土地转让模块设计
5.更好的开发体验
5.1。 Typescript + Router
除了传递字符串路径之外,我们是否可以像小程序内部的路由跳转函数调用那样使用页面跳转链接调用?相似的东西;
routes.pages.user.go({ name: 'jc' });
这样做的好处是:
- 更自然的通话方式。
- 可以与TS结合实现类型提示和链接。
因为wxapp-router
事先不知道开发者应该注册什么路由,所以TS路由声明文件必须由开发者定义。
例如我们在项目中维护一个路由文件:
// config/routes.ts // 创建路由实例 const router = new Router(); const routesConfig = [{ path: '/user', route: '/pages/user/index', }, { path: '/goods', route: '/pages/goods/index', }]; type RoutesType { paegs: { user: Route<{name: string}>, goods: Route, } } // 注册路由 router.batchRegister(routesConfig); // 获取 routes const routes: RoutesType = router.getRoutes(); export default routes;
,然后在其他地方使用:
import routes from './routes.ts'; routes.pages.user.go({ name: 'jc' });
5.2。智能创建路由配置
如果路由数量增加,我们还需要配置每个路由文件。手动写路由RoutesType
会有点不方便。
在小程序中,我们将正式路径配置为app.json
,然后结合已建立的项目结构,我们可以通过自动构建完成大部分工作,例如:
- 智能注册路由
- 智能参数声明识别页面
5.3.自定义组件跳转
以上都是在脚本级别使用的。小程序里还有wxml
。希望有一个可以快速使用的组件:
<Router path="/pageA" query="{{pageAQuery}}"></Router> <Router path="/pageB" query="{{pageBQuery}}" type="redirectTo"></Router> <Router path="/pageC/katy"></Router>
那么如果你实现一个自定义组件,然后包装Router模块,问题就不会太大了。
示例代码:
// components/router.wxml <view class="wxapp-router" bind:tap="gotoPage"> <slot /> </view>
// components/router.ts Component({ properties: { path: String, type: { type: String, value: 'gotoPage' }, route: String, query: Object, delta: Number, setData: Object, }, methods: { gotoPage(event) { const router = getApp().router; const { path, route, type, query} = this.data; const toPath = route || path; if (['gotoPage', 'navigateTo', 'switchTab', 'redirectTo'].includes(type)) { (router as any)[type](toPath, query); } if (type === 'navigateBack') { const { delta, setData } = this.data; router.navigateBack({ delta }, { setData }) } } } })
6。总体架构图
最后我们整体回顾一下各个模块的设计
- Navigator:封装微信原生路由API,提供智能跳转策略。
- LandTransfer:提供着陆页转移策略。
- RouteMatcher:提供动态路由参数匹配功能。
- 路由:为每条路由创建一个路由器。
- 路由器:集成内部模块,提供优雅的外部调用方式。
- 记录器:内部记录器。
- 正则表达式之路:来自开源社区的路由机制。
7。最后
考虑到我写了很多实用的文章,很多同学都想得到完整的示例代码,所以这次就简单写了一个工具,Enjoy!
wxapp-router: ?微信小程序路由
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。