关于前端:微信小程序路由实战

10次阅读

共计 6645 个字符,预计需要花费 17 分钟才能阅读完成。

欢送来到我博客浏览:BlueSun – 微信小程序路由实战

0. 目录

  • 1. 前言
  • 2. 智能路由跳转 — Navigator 模块
  • 3. 虚构路由策略 — Router 模块
  • 4. 落地直达策略 — LandTransfer 模块

    • 4.1. 对于要解决的第一个问题:对立的落地页
    • 4.2. 对于第二个要解决的问题:短链参数
    • 4.3. LandTransfer 模块设计
  • 5. 更好的开发体验

    • 5.1. Typescript + Router
    • 5.2. 智能生成路由配置
    • 5.3. 自定义组件跳转
  • 6. 整体架构图
  • 7. 最初的最初

1. 前言

在微信小程序由一个 App()实例,和泛滥 Page() 组成。而在小程序中所有页面的路由全副由框架进行治理,框架以栈的模式保护了所有页面,而后提供了以下 API 来进行路由之间的跳转:

  1. wx.navigateTo
  2. wx.redirectTo
  3. wx.navigateBack
  4. wx.switchTab
  5. wx.reLaunch

然而,对于一个企业应用,把这些问题留给了开发者:

  1. 原生 API 应用了 Callback 的函数实现模式,与咱们古代广泛的 Promiseasync/await 存在 gap。
  2. 基于小程序路由的设计,裸露给内部的是实在路由(如扫码,公众号链接等形式),对后续我的项目重构留下历史包袱。
  3. 小程序页面栈最多十层,在超过十层后 wx.navigateTo 生效,须要开发者判断应用 wx.redirectTo 或其余 API
  4. 小程序页面栈存在一种非凡的页面:Tab 页面,须要应用 wx.switchTab 能力跳转。须要开发者被动判断,不不便前期改变 Tab 页面属性。
  5. 额定的,对于小程序码,要应用无数量限度 API wxacode.getUnlimited,存在参数长度限度 32 位以内。须要开发者自行解决。

而本文,冀望能对这若干问题,一一提供解决方案。

2. 智能路由跳转 — Navigator 模块

在这里咱们一起解决:

  1. 原生 API 非 Promsie
  2. 页面栈冲破十层时非凡解决
  3. 非凡页面 Tab 的跳转解决

咱们的思路是,心愿能设计一种逻辑,依据场景来主动判断应用哪个微信路由 API,而后对外只提供一个函数,例如:

gotoPage('/pages/goods/index')

具体逻辑如下:

  1. 当跳转的路由为小程序 tab 页面时,则应用 wx.switchTab
  2. 当页面栈达到 10 层之后,如果要跳转的页面在页面栈中,应用 wx.navigateBack({delta: X}) 出栈到指标页面。
  3. 当页面栈达到 10 层之后,指标页面不存在页面栈中,应用 wx.redirectTo 替换栈顶页面。
  4. 其余状况应用 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. 虚构路由策略 — Router 模块

在这里,咱们解决:

  1. 对外裸露了实在路由,导致历史包袱惨重的问题。

在许多利用开发中,咱们常常须要把某种模式匹配到的所有路由,全都映射到同个页面中去。
例如,咱们有一个 Goods 页面,对于所有 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');

Class Router 的外围逻辑是实现:

  1. 路由的注册,实现「虚构门路」和「实在门路」关系的存储。
  2. 满足「虚构门路」到「实在门路」的转换,并且辨认「动静门路参数」(dynamic segment)。
  3. 路由跳转。

对于「路由的注册」,咱们在其外部存储一个 map 就能实现。

而对于「门路的转换」,vue-router 有相似的实现,通过其源码发现,外部是应用 path-to-regexp 作为门路匹配引擎,咱们能够拿来用之。

而后对于「路由的跳转」,咱们能够间接复用下面提到的 Navigator 模块,通过输出实在门路,来实现路由的跳转。

模块设计:

其中:

  1. RouteMatcher:提供动静路由参数匹配性能,外部应用 path-to-regexp 作为门路匹配引擎。
  2. Route: 为每个门路创立路由器,存储每个路由的虚构门路和实在路由的关系。
  3. Router:整合外部各模块,对外提供对立且优雅的调用形式。

4. 落地直达策略 — LandTransfer 模块

在这里,咱们解决:

  1. 小程序扫码、公众号链接等场景下的落地页对立。
  2. 小程序码,对于无限量 API wxacode.getUnlimited,冲破参数 32 位长度限度。

4.1. 对于要解决的第一个问题:对立的落地页

咱们把如:扫小程序码、公众号菜单、公众号文章等形式关上小程序某个页面的门路称为「内部路由」。

依据小程序的设计,裸露给内部的连贯是实在的页面门路,如:/pages/home/index,该设计在实践中存在的弊病:各个落地页扩散,前期批改实在文件门路难度大。

「中长生命周期」 产品中,随着产品的迭代,咱们难免会遇到我的项目的重构。如果散发进来的都是没通过解决的实在门路的话,咱们重构时就会束手束脚,要做很多的兼容操作。因为你不晓得,散发进来的小程序二维码,有多少被打印到实体物料中。

那么,「虚构路由」+「落地直达」 的策略就显得根本且重要了。

「虚构路由」的性能,Router 模块给咱们提供了反对了,咱们还须要对外提供一个对立的落地页面,让它来实现对外部路由的直达。

根本逻辑:

  1. 散发进来的实在路由,指向到惟一的落地页面,如:$LAND_PAGE: /pages/land-page/index
  2. 由这个落地页面,进行外部路由的重定向转发,通过接管 参数,如: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. 对于第二个要解决的问题:短链参数

微信小程序次要提供了两个接口去生成小程序码:

  1. wxacode.get: 获取小程序码,实用于须要的码数量较少的业务场景。通过该接口生成的小程序码,永恒无效,数量限度为 100,000
  2. wxacode.getUnlimited: 获取小程序码,实用于须要的码数量极多的业务场景。通过该接口生成的小程序码,永恒无效,数量暂无限度。

第一种形式,wxacode.get 数量限度为 10w 个,尽管量很大了,绝大多数的小程序可能用不到这个量。

但如果咱们经营的是一个中大型电商小程序的话,如果:1w 种商品 x 10 种商品规格,那就会超过这个数量。到时候再进行革新,就艰难了。

所以,如果抱着是经营一个 「中长生命周期」 的产品的话,咱们会应用第二种形式:wxacode.getUnlimited

不尽人意的是,尽管它没有数量限度,然而对参数会有 32 个字符的限度,显然是不够用的(一个 uuid 就 32 字符了)。

对于这种状况,咱们能够应用「短链参数」的模式解决,因为 wxacode.getUnlimited 会通过 scene字段作为 query 参数传递给小程序的,那么咱们能够通过 scene参数来实现短链服务,这须要后端配合。

前后端交互如下:

  1. 当小程序须要生成小程序码的时候,申请后端提供的接口,例如:/api/encodeShortParams
  2. 后端把内容转换为 32 字符内的字符串,存储到数据库中。
  3. 后端通过 wxacode.getUnlimited 接口,以短链字符串作为 scene的值,以约定好的对立落地页 $LAND_PAGE作为 page值,生成小程序码。
  4. 当通过小程序码进入小程序,小程序获取到 scene参数,申请后端提供的接口,例如:/api/decodeShrotParams
  5. 小程序理解内容,跳转到指标页面中去。

而前端对于对立落地页的逻辑解决,咱们只须要在第一个问题的根底上,减少一个 转换短链参数内容 的逻辑就行了:

代码层面上,咱们咱们只须要多定义转换短链参数的形式: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. LandTransfer 模块设计

5. 更好的开发体验

5.1. Typescript + Router

对于小程序外部的路由跳转,咱们除了指定一个字符串的路由,咱们是否也能够通过链式调用,像调用函数那样去跳转页面呢?相似这样;

routes.pages.user.go({name: 'jc'});

这样做的益处是:

  1. 更天然的调用形式。
  2. 能联合 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,那么在遵循既定的我的项目构造状况下,咱们能够通过主动构建,实现大部分工作,例如:

  1. 智能注册路由
  2. 智能辨认页面入参申明

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. 整体架构图

最初,咱们来整体回顾一下各模块的设计

  1. Navigator:封装微信原生路由 API,提供智能跳转策略。
  2. LandTransfer:提供落地页直达策略。
  3. RouteMatcher:提供动静路由参数匹配性能。
  4. Route: 为每个门路创立路由器。
  5. Router:整合外部各模块,对外提供优雅的调用形式。
  6. Logger:外部日志器。
  7. Path-to-regexp: 开源社区的路由匹配引擎。

7. 最初的最初

鉴于写过很多的实战类的文章,会有不少同学想要到整体的示例代码,这次我就索性写了一个工具,Enjoy it!

wxapp-router:🛵 The router for Wechat Miniprogram

正文完
 0