小程序Page全局路由管理

406 阅读2分钟

前言

考虑到项目功能的耦合性,以及后期要加入的全局状态管理功能,在开发router管理功能之前,先实现了小程序的插件管理,具体可查看文章小程序增加插件管理功能

背景

小程序本身并没有提供类似于vue-router的全局管理功能。

针对全局任务,比如进入页面前获取用户信息,大多数情况都是写在app.js中,由于异步的原因,实际页面中难以确保在onload中准确的拿到用户信息,如果将任务放到单个页面,那后期的维护将是难以想象的。

需求

  • 1.不会对现有页面逻辑造成变动。
  • 2.确保在onload执行之前,拿到用户信息。
  • 3.全局任务与页面逻辑解耦。
  • 4.使用成本低。

实现思路

参考vue-router路由管理,将全局任务放到路由守卫中,这样既不会对页面现有逻辑造成变动,也让全局任务与页面解耦。

1.仿vue-router实现Router管理类

负责全局任务的注册和管理

2.实现中间件流程管理

控制任务执行的流程

3.实现插件功能的install方法

提供给Page插件系统调用的方法,负责对 onLoadonShow 进行劫持管控。

具体实现

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
})

流程图

router管理.png

总结

这套功能目前只考虑到了Page函数中onLoad及onShow的拦截,并未考虑Behavior和Component,但大致思路一致,可根据实际项目情况,进行调整。