前言
考虑到项目功能的耦合性,以及后期要加入的全局状态管理功能,在开发router管理功能之前,先实现了小程序的插件管理,具体可查看文章小程序增加插件管理功能。
背景
小程序本身并没有提供类似于vue-router的全局管理功能。
针对全局任务,比如进入页面前获取用户信息,大多数情况都是写在app.js中,由于异步的原因,实际页面中难以确保在onload中准确的拿到用户信息,如果将任务放到单个页面,那后期的维护将是难以想象的。
需求
- 1.不会对现有页面逻辑造成变动。
- 2.确保在onload执行之前,拿到用户信息。
- 3.全局任务与页面逻辑解耦。
- 4.使用成本低。
实现思路
参考vue-router路由管理,将全局任务放到路由守卫中,这样既不会对页面现有逻辑造成变动,也让全局任务与页面解耦。
1.仿vue-router实现Router管理类
负责全局任务的注册和管理
2.实现中间件流程管理
控制任务执行的流程
3.实现插件功能的install方法
提供给Page插件系统调用的方法,负责对 onLoad 和 onShow 进行劫持管控。
具体实现
1.声明路由管理
声明了Router管理类,对外提供了 beforeLoad 全局加载前守卫以及 beforeShow 全局显示前守卫,beforeLoadHooks和beforeShowHooks负责保存全局任务,currentPage保存 了当前的Page实例。
export default class Router {
_currentPage = null
// page加载前
beforeLoadHooks = []
// page展示前
beforeShowHooks = []
beforeLoad (fn) {
this.beforeLoadHooks.push(fn)
}
beforeShow (fn) {
this.beforeShowHooks.push(fn)
}
get currentPage () {
return this._currentPage
}
set currentPage (page) {
this._currentPage = page
}
}
2.实现中间件管理功能
负责控制任务执行的流程,由于异步任务的存在,所以需要中间件进行管控,由用户调用next来决定何时进行下一个任务。
- middlewares 属性负责保存所有的中间件
- use 属性负责注册
- compose 属性负责组合流程
export default class Middleware {
middlewares = []
constructor (middlewares) {
this.middlewares = middlewares || []
}
use (fn) {
this.middlewares.push(fn)
}
compose () {
const { middlewares } = this
for (let fn of middlewares) {
if (typeof fn !== 'function') {
throw new TypeError('Middlewares must be composed of functions')
}
}
return function (next, ...args) {
let index = -1
function dispatch (i) {
if (i < index) {
throw new Error('next() called multiple times')
}
index = i
let fn = middlewares[i]
if (i === middlewares.length) {
fn = next
}
if (!fn) {
return
}
try {
return fn(dispatch.bind(null, i + 1), ...args)
} catch (error) {
throw new Error(error)
}
}
return dispatch(0)
}
}
}
3.实现插件功能install方法
提供给插件系统调用的方法,是整个路由管理的核心。插件系统调用install时,会传入当前用户的配置
- 首先通过getApp方法,获取到当前的router实例,如果不存在,则直接退出。
- 从router中拿到前置路由守卫,对用户传入的onload及onShow进行包装,使之支持next函数。
- 使用中间件对路由守卫和用户传入的方法进行组合,便于流程化控制。
import Middleware from '../Middleware'
/**
* 安装
* 包装Page
*/
export default function install (config = {}) {
const { router } = getApp()
if (router) {
const beforeLoadHooks = router.beforeLoadHooks || []
const beforeShowHooks = router.beforeShowHooks || []
const { onLoad, onShow } = config
// load
const newOnLoad = function (args, next) {
try {
onLoad && onLoad.call(this, args)
next()
} catch (error) {
throw new Error(error)
}
}
// 劫持
config.onLoad = function (args) {
const pages = getCurrentPages()
const pagesLength = pages.length
const oldPage = router.currentPage
router.currentPage = pages[pagesLength - 1]
try {
const dispatch = new Middleware([
...beforeLoadHooks,
newOnLoad.bind(this, args)
]).compose()
dispatch(null, router.currentPage, oldPage)
} catch (error) {
console.error(error)
}
}
// show
const newShow = function (next) {
try {
onShow && onShow.call(this)
next()
} catch (error) {
throw new Error(error)
}
}
// 劫持
config.onShow = function () {
const pages = getCurrentPages()
const pagesLength = pages.length
router.currentPage = pages[pagesLength - 1]
try {
const dispatch = new Middleware([
...beforeShowHooks,
newShow.bind(this)
]).compose()
dispatch(null, router.currentPage)
} catch (error) {
console.error(error)
}
}
}
}
4.初始化使用
router使用,与vue-router类似,先初始化,然后注册全局任务,最后调用Page.use将功能集成到系统中去。
import Router from '../common/router/index'
const router = new Router()
router.beforeLoad(async (next, to, from) => {
next()
})
router.beforeShow(async (next, to) => {
next()
})
Page.use(Router)
export default router
5.app.js引入
将当前router实例放到app中
import router from './router/index'
App({
router
})
流程图
总结
这套功能目前只考虑到了Page函数中onLoad及onShow的拦截,并未考虑Behavior和Component,但大致思路一致,可根据实际项目情况,进行调整。